laravel tests Invalid argument supplied for foreach()
If you faced the issue with laravel tests Invalid argument supplied for foreach(), this post might be helpful for you. Recently I was trying to test something, where I was facing this issue. I will write the code in details with the scenario, hope it will be helpful for you.
Table of Contents
Open Table of Contents
Scenario
I allow my user to create a post where user can able to choose category or categories for this post at a time. Meaning, during creating post it will store the categories belongs to this post.
Tables
The 3 tables structure are-
posts
- id
- title
- slug
- details
categories
- id
- title
- slug
category_post
- id
- post_id
- category_id
Relationships
I am showing the relationships between posts and categories.
App\Post.php
<?php
namespace App;
use App\Category;
use App\CategoryPost;
use Illuminate\Database\Eloquent\Model;
class Category extends Model
{
/**
* A post is belongs to a Category
*/
public function category()
{
return $this->hasMany(Category::class);
}
/**
* A post has many CategoryPost
*/
public function categoryPost()
{
return $this->hasMany(CategoryPost::class);
}
}
App\Category.php
<?php
namespace App;
use App\post;
use App\CategoryPost;
use Illuminate\Database\Eloquent\Model;
class Category extends Model
{
/**
* A Category has Many Posts
*/
public function post()
{
return $this->hasMany(Post::class);
}
/**
* A Category has many CategoryPost
*/
public function categoryPost()
{
return $this->hasMany(CategoryPost::class);
}
}
App\CategoryPost.php
<?php
namespace App;
use App\Post;
use App\Category;
use Illuminate\Database\Eloquent\Model;
class CategoryPost extends Model
{
/**
* A CategoryPost is belongs to a Category
*/
public function category()
{
return $this->belongsTo(Category::class);
}
/**
* A post has many CategoryPost
*/
public function post()
{
return $this->belongsTo(Post::class);
}
}
Writing Test.
Now I want to write a test that executes the following criteria-
- as a user, I should able to submit the add new post route that will generate-
- Create a new post
- Add selected categories in the category_post table
PostTest.php
/** @test */
public function as_a_logged_in_user_I_should_submit_the_create_new_post_form()
{
$this->withoutExceptionHandling();
$user = factory(User::class)->create([
'flag' => 1,
'password' => bcrypt($password = 'ILoveLaravel')
]);
$hasUser = $user ? true : false;
$this->assertTrue($hasUser);
$response = $this->post(route('login'), [
'email' => $user->email,
'password' => $password,
'flag' => 1
]);
$postData = [
'title' => $title = 'This is the title',
'slug' => str_slug($title),
'details' => 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. A aliquam aperiam autem, cum cumque cupiditate deleniti eius fugiat magnam, minima officiis porro qui quidem quis quo rem veniam. Et, ipsam.',
];
$eventResponse = $this->post(route('post.store'), $postData);
$eventResponse->assertStatus(201);
$eventResponse->assertRedirect(route('events.add'));
}
Real Coding
Now, let’s write some real coding in the controller. We have a dedicated controller for that called PostController
.
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'title' => 'required',
'details' => 'required',
]);
if ($validator->fails()) {
return redirect(route('post.add'))
->withErrors($validator)
->withInput();
};
// create a new Post
$post = Post::create([
'title' => $request->title,
'slug' => str_slug($request->title, '-'),
'details' => $request->details,
]);
// Redirect and Success Message.
}
Until now, the code and testing working fine. No issue what so ever in my side.
If you notice closely that, I didn’t store any category details in the category_post
table. Let’s add in the controller first.
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'title' => 'required',
'details' => 'required',
]);
if ($validator->fails()) {
return redirect(route('post.add'))
->withErrors($validator)
->withInput();
};
// create a new Post
$post = Post::create([
'title' => $request->title,
'slug' => str_slug($request->title, '-'),
'details' => $request->details,
]);
// add records in the category_post table.
// $request->categories will return [1,2,3] like this
foreach ($request->categories as $category){
CategoryPost::create([
'post_id' => $post->id,
'category_id' => $category,
]);
}
// Redirect and Success message.
}
Code is working fine in the browser. Now, let’s try in the unit testing.
vendor/bin/phpunit
And I believe you will see some sort of error called Invalid argument supplied for foreach() . I have found that. In the beginning, I didn’t have any single clue of it. After a deep research, I realize that it because $request->categories
is missing in the testing.
So, in the test $postData
, I have added a new array called categories
, and it will be like this-
$postData = [
'title' => $title = 'This is the title',
'slug' => str_slug($title),
'details' => 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. A aliquam aperiam autem, cum cumque cupiditate deleniti eius fugiat magnam, minima officiis porro qui quidem quis quo rem veniam. Et, ipsam.',
'categories' => $categories = [1,2,3,4]
];
The whole testing method will be like this-
/** @test */
public function as_a_logged_in_user_I_should_submit_the_create_new_post_form()
{
$this->withoutExceptionHandling();
$user = factory(User::class)->create([
'flag' => 1,
'password' => bcrypt($password = 'ILoveLaravel')
]);
$hasUser = $user ? true : false;
$this->assertTrue($hasUser);
$response = $this->post(route('login'), [
'email' => $user->email,
'password' => $password,
'flag' => 1
]);
$postData = [
'title' => $title = 'This is the title',
'slug' => str_slug($title),
'details' => 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. A aliquam aperiam autem, cum cumque cupiditate deleniti eius fugiat magnam, minima officiis porro qui quidem quis quo rem veniam. Et, ipsam.',
'categories' => $categories = [1,2,3,4]
];
$eventResponse = $this->post(route('post.store'), $postData);
$eventResponse->assertStatus(201);
$eventResponse->assertRedirect(route('events.add'));
}
Now if I run vendor/bin/phpunit
finally and I get everything is green.
Although this article is quite long, however, hope it will be helpful for you.
Thank you for reading.