Unit test Laravel pre-5.3 Queue facade

 Reading time ~3 minutes

Heads up: this article is over a year old. Some information might be out of date, as I don't always update older articles.

Introduction

This post is a followup of the previous one.

During my daily job I often work with old (pre 5.3) Laravel versions which do not provide fake implementations on facades, since this functionality has been introduced in version 5.3. For example with the Queue facade you can make the following assertions:

<?php

// Fake Queue functionality
Queue::fake();

// Perform order shipping...

Queue::assertPushed(ShipOrder::class, function ($job) use ($order) {
    return $job->order->id === $order->id;
});

// Assert a job was pushed to a given queue...
Queue::assertPushedOn('queue-name', ShipOrder::class);

// Assert a job was not pushed...
Queue::assertNotPushed(AnotherJob::class);

Queue Mocking for old Laravel 5 versions

As I wrote in the previous post, if you’re still using Laravel 5.1 or 5.2 you can mock the calls to the facade anyway, since the shouldReceive method will return an instance of a Mockery mock.

The problem that led me to write this post is that in one of my tests I had to make assertions on a queued job. This seems like an easy task, right?

<?php

Queue::shouldReceive('push')
    ->once()
    ->with(MyJob::class);

Unfortunately things are not so simple. First you need to mock also connection and connected methods, otherwise Mockery will complain that those methods are not defined on the mock object.

<?php
Queue::shouldReceive('connection')->andReturnSelf();
Queue::shouldReceive('connected');

But in my case there are also other places where the Queue facade is used, for example to keep synchronized the models with ElasticSearch (remeber that this is a pre-Laravel Scout version) and that was messing up tests, because the push method was called multiple times with different arguments.

Probably there are smarter ways to solve this problem. For example adding a configuration value to disable model - ES synchronization during tests, but what I really wanted was to know if it was possible to make assertions only on one particular function argument and just let the rest fall through.

Turns out that with Mockery you can tell a mocked method to return different values for different arguments, using the andReturnUsing() method with accepts one or more closures.

<?php

$mock = \Mockery::mock('MyClass');

$mock->shouldReceive('foo')->withArgs(function ($arg)
{
    if ($arg % 2 == 0) {
        return true;
    }
    return false;
});

$mock->foo(4); // returns true
$mock->foo(3); // returns false

I know, this is not much of an example, so let’s skip to our concrete need.

Using andReturnUsing() we can make specific assertions on the argument and just ignore the return value, since we are testing code that looks like the following:

<?php

Queue::push(new MyJob());

The final test is the following:

<?php

// counts how many times our job has been pushed into the queue
$called = 0;

Queue::shouldReceive('connection')->andReturnSelf();
Queue::shouldReceive('connected');

Queue::shouldReceive('push')->andReturnUsing(function ($argument) use ($called)
{
    if ($argument === MyJob::class)
    {
        $called++;
    }

    // ignore everything else
});

// assert that MyJob has been pushed once in the queue
$this->assertEquals(1, $called);

References:

  1. https://robertbasic.com/blog/mockery-return-values-based-on-arguments/
  2. http://docs.mockery.io/en/latest/reference/expectations.html#declaring-return-value-expectations
  3. https://divinglaravel.com/pushing-jobs-to-queue
comments powered by Disqus

Unit test Laravel pre-5.3 Mail facade

Introduction

This post is only serving as a reference to how the Mail facade should be unit tested in Laravel versions …

Effective pagination in AdonisJS