1
votes

I'm using Mockery in my Laravel project to mock the User Eloquent model and test a route.

This is how I test the /api/user/activate route:

<?php

use Illuminate\Support\Facades\Session;

class ActivateTest extends TestCase
{
    private $userMock;

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

        $this->userMock = Mockery::mock('App\User');

        Session::start();
    }

    public function tearDown()
    {
        Mockery::close();
    }

    public function testActivate()
    {
        $this->userMock->shouldReceive('where->first')->once()->andReturn('test');
        $this->userMock->shouldReceive('activate')->once();

        $response = $this->call('POST', '/api/user/activate', [
            'activationToken' => '838jfjnvu83u3',
            '_token' => csrf_token()
        ]);

        // This will be displayed in the PHPunit output
        print_r($response->getContent());

        $this->assertResponseStatus(200);
    }
}

The problem I'm having is that the andReturn('test') doesn't seem to work. The PHPunit result is:

F{"error":{"message":null,"statusCode":404}}

Time: 276 ms, Memory: 15.50Mb

There was 1 failure:

1) ActivateTest::testActivate
Failed asserting that 404 matches expected 200.

This is the content of the activate() in the UserController:

public function activate(Request $request)
{
    $activation = $request->input();

    $user = $this->user->where('activationToken', $activation['activationToken'])->first();

    if(!$user) return $this->respondNotFound($user);

    try
    {
        $user->activate($activation['password']);
    }
    catch(ModelException $e)
    {
        return $this->respondInternalError($e->errorMessages());
    };

    return $this->respondCreated('Account activated.');
}

The problem is that $user in the controller is null because the mock is not returning test (in that case the condition would evaluate to true and I wouldn't get a 404 response).

Edit:

I also tried using PHPunit mocking but it wasn't successful:

$this->userMock = $this->getMockBuilder('App\User')->setMethods(['where', 'first', 'activate'])->getMock();

$this->userMock->expects($this->once())->method('where')->willReturn($this->userMock);
$this->userMock->expects($this->once())->method('first')->willReturn('test');
$this->userMock->expects($this->once())->method('activate');
2
I'm not very known with Mockery yet, but i think that the where->first is not possible because it's not a valid function. The function/attr called on the user object is where and that returns a object which you call first() on..Sven van Zoelen
Well looking at this question it looks like I can do that.siannone
I don't see any ..->shouldReceive('where->first') in the provided question. Only the stub answer has something about it but thats an extension or something.Sven van Zoelen
You can do shouldReceive('select->where->runQuery->fetch') if you do not care about the arguments. Anyway, analyzing the code I noticed what could be causing the issue. Probably the fact that the function first() is not actually an eloquent model function is causing the issue.siannone
Ah thx, didn't know that chaining was possible. Will try that out!Sven van Zoelen

2 Answers

0
votes

It's not enough to mock an object. You need to get that mocked object to be injected into the class which contains that activate() function.

You can do that in your setUp() function as well. Try adding this...

$this->app->instance('App/User', $this->userMock);

That will tell Laravel when you want to inject an instance of App/User, to inject the mock object you just created instead.

0
votes

The issue was caused by ->first() since it's not a method existing neither on the Eloquent or User classes.

To solve it I created a new UserRepository and injected it as a dependency in the controller constructor.

class UserRepository implements UserRepositoryInterface
{
    /**
     * @var User
     */
    protected $user;

    /**
     * @param User $user
     */
    public function __construct(User $user)
    {
        $this->user = $user;
    }

    /**
     * @param $activationToken
     * @return mixed
     */
    public function whereActivationToken($activationToken)
    {
        return $this->user->where('activationToken', $activationToken)->first();
    }
}

Injection in the UserController:

public function __construct(UserRepository $userRepository)
{
    $this->userRepository = $userRepository;
}

And this is how the test PostActivateTest class looks like now:

use Illuminate\Support\Facades\Session;

class PostActivateTest extends TestCase
{
    private $user;
    private $userRepositoryMock;

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

        $this->user               = Mockery::mock('App\User');
        $this->userRepositoryMock = Mockery::mock('Repository\Database\UserRepository');
        $this->app->instance('App\User', $this->user);
        $this->app->instance('Bloom\BloomCRM\Repository\Database\UserRepository', $this->userRepositoryMock);

        Session::start();
    }

    public function tearDown()
    {
        Mockery::close();
    }

    public function testActivate()
    {
        $this->userRepositoryMock->shouldReceive('whereActivationToken')->once()->andReturn($this->user);
        $this->user->shouldReceive('activate')->once();

        $this->call('POST', '/api/user/activate', [
            'activationToken' => '838jfjnvu83u3',
            'password' => 'test',
            '_token' => csrf_token()
        ]);

        $this->assertResponseStatus(201);
    }
}