bambamboole's avatar

How to test a View Composer in Laravel?

I want to test a view composer and I don't get how to...

I have a simple View Composer like this:

class SidebarComposer
{
    /**
     * @var Post
     */
    protected $post;

    public function __construct(Post $post)
    {
        $this->post = $post;
    }

    public function compose(View $view)
    {
        $view->with('latestPosts', $this->post->orderBy('created_at', 'desc')->take(5)->get());
    }
}

And my test looks like that

class SidebarComposerTest extends TestCase
{
    use DatabaseMigrations;
    /** @test */
    public function it_passes_the_latest_posts_to_the_view()
    {
        $posts = factory(Post::class,5)->create();
        $composer = app(SidebarComposer::class);
        $view = \Mockery::mock(View::class);

        $view->shouldReceive('with')->with(['latestPosts', $posts])->once();

        $composer->compose($view);
    }
}

The phpunit error I get is this :

Mockery\Exception\NoMatchingExpectationException : No matching handler found for Mockery_0_Illuminate_View_View::with('latestPosts', object(Illuminate\Database\Eloquent\Collection)). Either the method was unexpected or its arguments matched no expected argument list for this method

Does anyone have any suggestions on how to test the ViewComposer correctly?

0 likes
4 replies
davielee's avatar

I believe instead of passing an array to ->with() you should instead call it like this.

$view->shouldReceive('with')->with('latestPosts', $posts)->once();

The reason for that is that each argument passed to ->with() is meant to map to an argument in the function that is supposed to receive it. Therefore your mock is expecting to receive an array with two items instead of two separate arguments.

bambamboole's avatar
bambamboole
OP
Best Answer
Level 10

I found a solution:

class SidebarComposerTest extends TestCase
{

    /**
     * @test
     */
    public function it_passes_the_latest_posts_to_the_view(): void
    {
        $latestPosts = [1,2,3,4,5];
        $post = \Mockery::mock(Post::class);
        $post->shouldReceive('getLatestPosts')->andReturn($latestPosts);
        $composer = new SidebarComposer($post);
        $view = \Mockery::spy(View::class);

        $composer->compose($view);

        $view->shouldHaveReceived('with')->with('latestPosts', $latestPosts);
    }
}

If someone has a better idea, im open for input :-)

impbob36's avatar

Rather than setting up mocks, I create a temporary test route:

class HeaderViewTest extends TestCase
{
    protected $route;
    protected $view;

    public function setUp()
    {
        // SETUP:       parent
        parent::setUp();

        // SETUP:       View file to test
        $this->view = 'layout.header';

        // SETUP:       Temporary route which returns view page
        $this->route = 'phpunit/HeaderViewTest ';
        app('router')->get($this->route, [function () {
            return view($this->view);
        }]);
    }


    /**
     * @test
     */
    public function assert_header_view_has_foo()
    {
        $this->get($this->route)
            ->assertViewHas('foo', 'bar');
    }
}

The tests here read a lot more cleaner to me. And i can include other 'view' test such as:

$this->assertViewHasAll(array $data);
$this->assertViewMissing($key);
$this->assertSee($value); 
$this->assertSeeText($value);

https://laravel.com/docs/5.5/http-tests#available-assertions

1 like

Please or to participate in this conversation.