milewski's avatar

Testing Controller - Unit Test

in my user controller i have

/**
     * Update the specified resource in storage.
     *
     * @param ProfileEdition $request
     * @return \Illuminate\Http\RedirectResponse
     */
    public function update(ProfileEdition $request)
    {
        $command = new UpdateUserProfileCommand($request->user(), $request->only('name', 'email'));
        $this->dispatch($command);

        return redirect()->route('user.profile');
    }

i want to Unit Test it... i have my unit test which test against... if the profile was indeed updated.. and if it was redirected back to the profile page.. but.. how could i test against if the command fired/dispatched?

we have there expectsJobs, expectsEvents but no expectsCommands...

0 likes
9 replies
bobbybouwmann's avatar

This is already tested by Laravel as far as I know, so no need to test it twice. The test you perform right now are the only once that are necessary here!

davorminchorov's avatar

Last time I heard, stuff that is already tested, should be tested with integration tests, not unit tests.

1 like
milewski's avatar

@bobbybouwmann okay but let`s say in UpdateUserProfileCommand i have a call to my UserRepository->update ... as long as im in the countroller.. how could i test that the updated method on UserRepository was fired?? i have this on my test

/** @test */
public function it_updates_user_profile()
{
    $this->withoutMiddleware();

    $this->be(User::first());

    $data = [
        'name' => 'Updated Name',
        'email' => 'updated@email.com'
    ];

    $this->route('POST', 'user.profile', $data);

    //here i wanna check if at least the updated method on my userRepository which was dispatched on my controller through the command bus was fired...
    
    $this->assertRedirectedToRoute('user.profile');
    $this->seeInDatabase('users', ['name' => 'Updated Name']);
}
bobbybouwmann's avatar

You would test your repository in a different test. Your controller will only check if your profile is updated or not. If your profile is not updated, then you know your repository test will probably fail as well.

pmall's avatar

how could i test that the updated method on UserRepository was fired??

If you want fine grained testing you should test all classes independently of the context. It means, mock their dependencies, run a method of this class with some parameters and test if the right methods of its dependencies are called with the right parameters.

You test your command by running the handle method with some parameters and test if the repository update method is actually called with the right parameters.

Then for the repository, you test that when you call update with some parameters the table is actually updated with those parameters.

Finally for testing your controller, you test when this action is run with some request parameters, a command is actually dispatched with these request parameters.

With the combination of those three tests you know the flow of updating a profile go well, and if it fails you know exactly where.

Again, it is fine grained testing. You can just test on a higher level if the profile is actually updated when you run an update request to this url.

milewski's avatar

@pmall thanks im just getting started on testing my code... i understood the need of testing every part separately with its own tests.. i just dont know how to Finally for testing your controller, you test when this action is run with some request parameters, a command is actually dispatched with these request parameters. i think that is what i wanted to say from the begining i just want to know if the command was dispatched... or not... i have tried stuff like this so far

/** @test */
public function it_updates_user_profile()
{
    $this->withoutMiddleware();

    $this->be(User::first());

    $data = [
        'name' => 'Updated Name',
        'email' => 'updated@email.com'
    ];

    //Before the Post occur i doing this.. but with no success

    $mock = Mockery::mock(Dispacher::class);
    $this->app->instance(Dispacher::class, $mock);

    $this->route('POST', 'user.profile', $data);

    //then after the post occur and the command is dispatched 

    $mock->shouldReceive('dispatch')->once(); // but it returns was expected 1 but 0 .... 
    
    $this->assertRedirectedToRoute('user.profile');
    $this->seeInDatabase('users', ['name' => 'Updated Name']);
}
milewski's avatar

@Corez64 actually i am using the commands and jobs at the same time.. command i use for stuff that might run at the moment it is dispatched and jobs for things should be queued... and i just try using

$this->expectsJobs(UpdateUserProfileCommand::class);

and yeah!! ... it worked!.. but broke another part of the code

$this->seeInDatabase('users', ['name' => 'Updated Name']);

maybe i am guessing that at the point i fire the expectsJobs.. it will mock it and replace on the Ioc Container then it will never get really executed for real which wouldn't insert the data into the db... now how to do... >.<

Corez64's avatar
Corez64
Best Answer
Level 37

@milewski maybe i am guessing that at the point i fire the expectsJobs.. it will mock it and replace on the Ioc Container then it will never get really executed for real which wouldn't insert the data into the db... now how to do... >.<

That is correct and is how a unit test should be, you just need to write another unit test for your job. Also if you are truly doing unit tests you shouldn't be talking to your database either. You seem to be doing some hybrid of unit tests and functional tests.

Unit tests should be something like this:

/** @test */
function controller_should_update_user()
{
    $this->withoutMiddleware();

    $this->be(User::first());

    $data = [
        'name' => 'Updated Name',
        'email' => 'updated@email.com'
    ];

    $this->expectsJobs(UpdateUserProfileCommand::class);

    $this->route('POST', 'user.profile', $data);

    $this->assertRedirectedToRoute('user.profile');
}

/** @test */
function update_user_profile_command_should_save_user()
{
    $data = [
        'name' => 'Updated Name',
        'email' => 'updated@email.com'
    ];

    $user = \Mockery::mock(User::class);
    $user->shouldReceive('update')->with($data);
        
    (new UpdateUserProfileCommand($user, $data))->handle();
}

Functional tests should look something like this:

/** @test */
public function it_updates_user_profile()
{
    $this->withoutMiddleware();

    $this->be(User::first());

    $data = [
        'name' => 'Updated Name',
        'email' => 'updated@email.com'
    ];

    $this->route('POST', 'user.profile', $data);

    $this->assertRedirectedToRoute('user.profile');
    $this->seeInDatabase('users', ['name' => 'Updated Name']);
}

The idea is that unit tests can run really quick so you run them often/all the time and functional tests take long so you run them less often but regularly.

I tend not to write unit tests for my controllers as I rarely have any logic in them worth testing as it normally gets passed of to jobs or something similar. Functional tests on the other hand do test my controllers and they just ensure that they, and everything they talk to work correctly.

3 likes

Please or to participate in this conversation.