Menu

Sherri Flemings

Software Swiss Army Knife • Textile Hobbyist • Gamer • Foodie •

Laravel, Codeception and Mockery

I ran into an issue while trying to mock a dependency in my controller class. Basically my test was ignoring my mocked class and loading the real thing, hitting a production service api. With a quick bit of googling I was reminded that we need to bind the mock object to Laravel's service container (thanks to this stack overflow comment).

I'm using the Laravel5 Module for Codeception to run my tests. My first attempt at solving this issue involved calling getApplication to access the Laravel application object, then bind the mock instance to the service container. This worked as long as I didn't have any mock expectations after a redirect or form submit. I learned that doing it this way caused the mock to be reset after the first request was sent.

After a bit more digging into the docs, I noticed this method named haveInstance which will do what I originally intended to do above. This will bind the mock to the service container and stay bound for the duration of your test so if you have mock expectatons to validate after a redirect or form submission they'll work now. No more calling live services during automated tests. Easy peasy!


$serviceAPIMock = Mockery::mock(\ServiceAPI::class);

// this was my original attempt BUT the mock gets reset after a form submit or redirect
// $I->getApplication()->instance( \ServiceAPI::class, $serviceAPIMock );

// The correct way to do this in Codeception
$I->haveInstance( \ServiceAPI::class, $serviceAPIMock );

I've expanded the code below in case you're curious and want to see the snippet used in context.

BetaUserController.php


<?php
namespace StitchCrafter\Http\Controllers;

use Illuminate\Database\QueryException;
use StitchCrafter\Http\Requests\RegisterBetaUser;
use StitchCrafter\Service\BetaUserService;
use Validator;

class BetaController extends Controller
{
    protected $betaUserService;

    // Laravel automagically binds this dependency to the service container
    public function __construct( BetaUserService $betaUserService )
    {
         $this->betaUserService = $betaUserService;
    }
    
    /**
     * Handle a beta registration request for the application.
     *
     * @param RegisterBetaUser $request
     * @return \Illuminate\Http\Response
     */
    public function registerBetaUser( RegisterBetaUser $request )
    {
        try
        {
            $userData = $request->all();
           
            // create a new user
            $betaUser = $this->createUser( $userData );
            $betaUser = $this->betaUserService->addUserToBetaList( $betaUser );

            return redirect()->route('beta.thanks.show')
              ->with( 'user', $betaUser->attributesToArray() );
        }
        catch( QueryException $ex )
        {
            return back()->withInput()->with( 'errors', ['email', 'Email is already registered'] );
        }
    }
}

BetaUserCest.php


<?php


use Helper\Functional;
use Mockery;

class BetaUserCest
{        
    public function it_shows_correct_value_for_beta_accounts_remaining( FunctionalTester $I )
    {
        $expected = env( 'MAX_BETA_ACCOUNTS' ) - 10;
        
        $betaUserService = Mockery::mock( \StitchCrafter\Service\BetaUserService::class );
        $betaUserService->shouldReceive('countBetaUsers')->andReturn( 10 );

        // load the mock into the service container
        $I->haveInstance( \ServiceAPI::class, $serviceAPIMock );

        $I->amOnPage( "/beta/register" );
        $I->see( "only $expected beta accounts available" );
    }
    
    public function it_registers_new_beta_user( FunctionalTester $I )
    {
        $betaUser = factory(\StitchCrafter\User::class)->make();
        $betaUserService = Mockery::mock(\StitchCrafter\Service\BetaUserService::class);
        $betaUserService->shouldReceive('countBetaUsers');
        $betaUserService->shouldReceive('addUserToBetaList')->andReturn($betaUser);
        
        // load the mock into the service container
        $I->haveInstance(\StitchCrafter\Service\BetaUserService::class, $betaUserService);
        
        $I->amOnPage("/beta/register");
        
        $I->fillField('name', $betaUser->name);
        $I->fillField('email', $betaUser->email);
        $I->click('Join the Private Beta!');
        $I->seeInCurrentUrl("beta/thanks");
        $I->seeRecord('users', array('name' => $betaUser->name, 'email' => $betaUser->email));
        $I->see( "Thanks " . $betaUser->name );
    }
}

comments powered by Disqus