David Llop

Back-End Developer

Written on

A different way to test your Jobs

UPDATE 2018-06-27: I learned a lot about queues and testing since I wrote this post. I no longer test my jobs or queues like I've described in here, but it has some interesting points that prevent me for deleting this article. If you want to test your jobs this way, of course you can, but let me tell you that there's a better and easier way to do this

I recently entered the huge world of executing Laravel jobs in background with Queues.

It was confusing initially because I had no experience on this topic and there was a lot of information on the internet and I didn't know where to start.

So, one step at a time.

The first big problem I faced was

How can I test that an API call really sends the job it creates to a queue?

I solved this problem was to switch the queue from redis to the database driver in your's .env file

QUEUE_DRIVER=database

After that, you can obtain that table with the following command:

php artisan queue:table

(Probably you will need the table for failed_jobs too)

php artisan queue:failed-table

Testing

In your tests, you can check the contents of the table:

/** @test */
public function register_user_call_created_send_email_job()
{
    $response = $this->json('POST', '/api/user/register', [
    	'name' => 'John',
    	'surname' => 'Doe',
    	'email' => 'john@doe.com'
    ];
    
    $response->assertStatus(201);
    
    $this->assertDatabaseHas('jobs', [
    	'queue' => 'register-emails'
    ]);
}

After this, I wanted to test the contents of the job, and the Job methods themselves. I created a model Job and added the following code to it:

class Job extends Model
{
    // This will store the unserialized command, acting like a Singleton
    protected $unserializedCommand;

    public function getNameAttribute()
    {
        return $this->payload->displayName;
    }

    // Decode the content of the payload since it's stored as JSON
    public function getPayloadAttribute()
    {
        return json_decode($this->attributes['payload']);
    }
	
	// Since Laravel stores the command serialized in database, this undo that serialization 
    // and save the result into the singleton property to prevent unserializing again 
    // (since it's a hard task)
    public function getCommandAttribute()
    {
        if (! $this->unserializedCommand) {
            $this->unserializedCommand = unserialize($this->payload->data->command);
        }

        return $this->unserializedCommand;
    }
}

Accessors are a really powerful Laravel feature, one of the most I love. You can use them to transform data, create new data on the fly, or even get fields from a database you know that needs to be refactorized

Using this new model, I'm able to test the content of the Job like this:

/** @test */
public function register_mail_job_receives_the_correct_email()
{
    $response = $this->json('POST', '/api/user/register', [
    	'name' => 'John',
    	'surname' => 'Doe',
    	'email' => 'john@doe.com'
    ];
    
    $response->assertStatus(201);
    
    $job = App\Models\Job::first();
    
    $this->assertEquals('john@doe.com', $job->command->email);
}

You'll need to make sure that your job properties are public in order to check them, or create getters instead.

Tinkering

If you're not using tests but still wants to see what's inside your job, you can use Laravel's awesome tinker command like this:

$ php artisan tinker
> $job = App\Models\Job::first();
> $job->command->email;
'john@doe.com'
>

Back to production

Once you have finished debugging and testing your jobs, you can switch back to redis by changing your env's variable QUEUE_DRIVER to redis.

This will result in another problem: your tests will stop working.

To fix this, you can change your environment variable in your phpunit.xml file:

<env name="QUEUE_DRIVER" value="database"/>

Spotted a mistake? Noticed something to improve? Feel free to edit this post on GitHub.


Special thanks 🙏 to Dieter Stinglhamber for the amazing code of this blog