Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mocking Request in Laravel 5.1 for (actual) Unit Test

First of all, I'm aware the docs states:

Note: You should not mock the Request facade. Instead, pass the input you desire into the HTTP helper methods such as call and post when running your test.

But those kind of tests are more like integration or functional since even though you're testing a controller (the SUT), you're not decoupling it from it's dependencies (Request and others, more on this later).

So what I'm doing, in order to do the correct TDD loop, is mocking the Repository, Response and Request (which I have problems with).

My test look like this:

public function test__it_shows_a_list_of_categories() {
    $categories = [];
    $this->repositoryMock->shouldReceive('getAll')
        ->withNoArgs()
        ->once()
        ->andReturn($categories);
    Response::shouldReceive('view')
        ->once()
        ->with('categories.admin.index')
        ->andReturnSelf();
    Response::shouldReceive('with')
        ->once()
        ->with('categories', $categories)
        ->andReturnSelf();

    $this->sut->index();

    // Assertions as mock expectations
}

This works perfectly fine, and they follow the Arrange, Act, Assert style.

The problem is with Request, like the following:

public function test__it_stores_a_category() {
    Redirect::shouldReceive('route')
        ->once()
        ->with('categories.admin.index')
        ->andReturnSelf();

    Request::shouldReceive('only')
        ->once()
        ->with('name')
        ->andReturn(['name' => 'foo']);

    $this->repositoryMock->shouldReceive('create')
        ->once()
        ->with(['name' => 'foo']);

    // Laravel facades wont expose Mockery#getMock() so this is a hackz
    // in order to pass mocked dependency to the controller's method
    $this->sut->store(Request::getFacadeRoot());

    // Assertions as mock expectations
}

As you can see I have mocked Request::only('name') call. But when I run $ phpunit I get the following error:

BadMethodCallException: Method Mockery_3_Illuminate_Http_Request::setUserResolver() does not exist on this mock object

Since I'm not calling directly setUserResolver() from my controller, that means it is called directly by the implementation of Request. But why? I mocked the method call, it shouldn't be calling any dependency.

What am I doing wrong here, why am I getting this error message?

PS: As a bonus, am I looking it the wrong way by forcing TDD with Unit Tests on Laravel framework, as it seems the documentation is oriented to integration testing by coupling interaction between dependencies and SUT with $this->call()?

like image 840
Christopher Francisco Avatar asked Aug 05 '15 02:08

Christopher Francisco


3 Answers

I tried mocking the Request for my tests too with no success. Here is how I test if item is saved:

public function test__it_stores_a_category() {
    $this->action(
            'POST',
            'CategoryController@store',
            [],
            [
                'name' => 'foo',
            ]
        );

    $this->assertRedirectedTo('categories/admin/index');

    $this->seeInDatabase('categories', ['name' => 'foo']);
}

Hope it is of some help

like image 105
Ross_102 Avatar answered Feb 17 '23 04:02

Ross_102


Unit testing a controller when using Laravel doesn't seem like a great idea. I wouldn't be concerned with the individual methods that are being called on the Request, Response and maybe even the repository classes, given the context of a Controller.

Furthermore, unit testing a controller belonging to a framework because you want to decouple the test sut from its dependencies doesn't make sense, as you can only use that controller in that framework with those given dependencies.

As the request, response and other classes are all fully tested (either via the underlying Symfony class, or by Laravel itself), as a developer I'd concern myself only with testing the code I own.

I'd write an acceptance test.

<?php

use App\User;
use App\Page;
use App\Template;
use App\PageType;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class CategoryControllerTest extends TestCase
{
    use DatabaseTransactions;

    /** @test */
    public function test__it_shows_a_paginated_list_of_categories()
    {
        // Arrange
        $categories = factory(Category::class, 30)->create();

        // Act
        $this->visit('/categories')

        // Assert
            ->see('Total categories: 30')
            // Additional assertions to verify the right categories can be seen may be a useful additional test
            ->seePageIs('/categories')
            ->click('Next')
            ->seePageIs('/categories?page=2')
            ->click('Previous')
            ->seePageIs('/categories?page=1');
    }

}

Because this test uses the DatabaseTransactions trait, it's easy to perform the arrange part of the process, which almost allows you to read this as a pseudo-unit test (but that's a slight stretch of the imagination).

Most importantly, this test verifies that my expectations are met. My test is called test_it_shows_a_paginated_list_of_categories and my version of the test does exactly that. I feel the unit test route only asserts that a bunch of methods are called, but doesn't ever verify that I'm showing a list of the given categories on a page.

like image 29
Amo Avatar answered Feb 17 '23 04:02

Amo


You will always run into problems trying to properly unit test controllers. I recommend acceptance testing them with something like Codeception. Using acceptance tests you can ensure your controllers/views are handling any data appropriately.

like image 36
Dylan Buth Avatar answered Feb 17 '23 05:02

Dylan Buth