laravel tests Invalid argument supplied for foreach()

02 Sep, 2019

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.

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

hasMany(Category::class);
    }

    /**
     * A post has many CategoryPost
     */
    public function categoryPost()
    {
        return $this->hasMany(CategoryPost::class);
    }
}

App\Category.php

hasMany(Post::class);
    }

    /**
     * A Category has many CategoryPost
     */
    public function categoryPost()
    {
        return $this->hasMany(CategoryPost::class);
    }
}

App\CategoryPost.php

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.

Hello. I’m Thouhedul Islam. Some people also know me as Suchi.

I am a Full-stack Developer. I love to play with PHP specially Laravel, JavaScript, MySql and latest web technology. Currently I am working for CartX as my day time job.

At night I love to solve problem for community. I am quite active at Laracasts