★ Handcrafting mocks
Tags: php, testing, laravel
Published: 2020-01-02 23:13:27

---
title: ★ Handcrafting mocks
published: true
date: 2018-12-12 17:36:15 UTC
tags: php, testing, laravel
canonical_url: https://freek.dev/1232-handcrafting-mocks
---

In an application I was working on I wanted to implement automated tweets. Of course, this logic should also be tested. In this blogpost I'd like to show you how you can easily handcraft your own mocks.

## Setting things up #

Let's first take a look at how you can tweet something in PHP. We're going to use Laravel in this example. In that framework, it's common to set things up in a service provider. To authenticate we're going to use [the popular `abraham/twitteroauth` package](https://packagist.org/packages/abraham/twitteroauth). In the service provider below we're making sure that whenever we resolve an instance of `App\Services\Twitter` (we're going to take a look at that soon). It gets a configured `TwitterOAuth` injected via its constructor.

```php
namespace App\Services\Twitter;

use Abraham\TwitterOAuth\TwitterOAuth;
use Illuminate\Support\ServiceProvider;

class TwitterServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->bind(Twitter::class, function () {
            $connection = new TwitterOAuth(
                config('services.twitter.consumer_key'),
                config('services.twitter.consumer_secret'),
                config('services.twitter.access_token'),
                config('services.twitter.access_token_secret')
            );

            return new Twitter($connection);
        });
    }
}
```

Let's look at the actual Twitter class. We just have to call the `statuses/update` endpoint to actually tweet something out.

```php
namespace App\Services\Twitter;

use Abraham\TwitterOAuth\TwitterOAuth;

class Twitter
{
    /** @var \Abraham\TwitterOAuth\TwitterOAuth */
    protected $twitter;

    public function __construct(TwitterOAuth $twitter)
    {
        $this->twitter = $twitter;
    }

    public function tweet(string $status)
    {
        return $this->twitter->post('statuses/update', ['status' => $status]);
    }
}
```

Imagine you want to tweet something out as soon as a blog post is published. There are multiple ways to code up that behaviour. In the example below if chosen to just fire off an event when the blog post has been published.

```php
use Illuminate\Database\Eloquent\Model;

class BlogPost extends Model {
  
  // ...

   public function publish()
   {
	  $this->published_at = now();
	  $this->save();

	  event(new BlogPostPublished($this));

	  return $this;
   }
  
   public function tweetText(): string
   {
       return "Here's my new blogpost: {$this->url}";
   }
}
```

In our app we’ve [set up an event listener](https://laravel.com/docs/5.7/events) that will actually send the tweet when the `BlogPostPublished` event is fired. I'm not going to include that code in this blog post as it's not relevant to the test we're going to perform.

## How to test that the tweet is sent? #

In our test, we want to make sure that when publishing something the tweet is actually sent. Of course, we don't want actually tweet stuff when running our tests. This would make our tests slow and our followers nuts.

```php
namespace Tests\Models;

use App\Models\Event;
use Tests\TestCase;

class BlogPostTest extends TestCase
{
    /** @test */
    public function it_will_send_a_tweet_when_publishing_a_post()
	{
        $blogPost = factory(BlogPost::class)->create();
        
        $blogPost->publish();
        
        // how to assert the tweet was sent?
    }
}
```

Now your first thought could be to reach [PHPUnit's built-in mocking solution](https://phpunit.de/manual/6.5/en/test-doubles.html) or [Mockery](https://github.com/mockery/mockery). While these options are perfectly valid, I don't like the fact that adding specific asserts isn't that readable in most cases.

You could also use facades to mock Twitter. This technique is demonstrated [Adam Wathan's Test Driven Laravel course](https://course.testdrivenlaravel.com/). The idea is that you swap out the actual `Twitter` class with another implementation you have control. This swapping is done via a facade. While this is also a valid approach you need to do some setup to make it work.

## Handcrafting mocks #

For this small app, I wanted to have the testing as light as possible. It turns out that creating a mock of your own is very simple. You can extend the original class and replace methods with your own implementation.

The trick to making this work is to add an empty constructor in there. This empty constructor will allow this class to be created without having to pass the `TwitterOAuth` that's required for the base class.

```php
namespace Tests\Mocks;

use PHPUnit\Framework\TestCase;

class Twitter extends \App\Services\Twitter\Twitter
{
    /** @var array */
    protected $sentTweets = [];

    /*
     * This avoids having to pass the constructor parameters defined in the base class
     */
    public function __construct()
    {
    }
    
    public function tweet(string $status)
    {
        $this->sentTweets[] = $status;
    }

    public function assertTweetSent(string $status)
    {
        TestCase::assertContains($status, $this->sentTweets, "Tweet `{$status}` was not sent");
    }
}
```

With this handcrafted mock, testing that the tweet is sent becomes very easy.

```php
namespace Tests\Models;

use App\Models\BlogPost;
use Tests\TestCase;
use App\Services\Twitter\Twitter;
use Tests\Mocks\Twitter as TwitterMock;

class BlogPostTest extends TestCase
{
    /** @test */
    public function it_will_send_a_tweet_when_publishing_a_post()
    {
        $twitter = $this->app->bind(Twitter::class, function () {
            return new TwitterMock();
        });

        $blogPost = factory(BlogPost::class)->create();

        $blogPost->publish();

        $twitter->assertTweetSent($blogPost->tweetText());
    }
}
```

If you want to mock Twitter in other tests as well you could move that binding logic to a `fakeTwitter` method on the base `TestCase`.

```php
namespace Tests;

use App\Services\Twitter\Twitter;

use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Tests\Mocks\Twitter as TwitterMock;

abstract class TestCase extends BaseTestCase
{
    // …

    protected function fakeTwitter(): TwitterMock
    {
        $twitter = $this->app->bind(Twitter::class, function() {
           return new TwitterMock();
        });   
                
        return $twitter;
    }
}
```

You can clean that up even more by using Laravel's native `InteractsWithContainer` trait. It contains a method to easily swap an implementation in the container.

```php
namespace Tests;

use App\Services\Twitter\Twitter;

use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Tests\Mocks\Twitter as TwitterMock;
use Illuminate\Foundation\Testing\Concerns\InteractsWithContainer;

abstract class TestCase extends BaseTestCase
{
    use InteractsWithContainer;

    // ...

    protected function fakeTwitter(): TwitterMock
    {
        return $this->swap(Twitter::class, new TwitterMock());
    }
}
```

With that `fakeTwitter` method in place, your test can be refactored to this:

```php
namespace Tests\Models;

use App\Models\BlogPost;
use Tests\TestCase;
use App\Services\Twitter\Twitter;
use Tests\Mocks\Twitter as TwitterMock;

class BlogPostTest extends TestCase
{
    /** @var \Tests\Mocks\Twitter */
    protected $twitter;

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

        $this->twitter = $this->fakeTwitter();
    }

    /** @test */
    public function it_will_send_a_tweet_when_publishing_a_post()
    {
        $blogPost = factory(BlogPost::class)->create();

        $blogPost->publish();

        $this->twitter->assertTweetSent($blogPost->tweetText);
    }

    // moarrr tests
}
```

If you need additional assertions in other tests it's easy to add them to your mock.

```php
public function assertTweetsSentCount(int $count)
{
    TestCase::assertCount($count, $this->tweets);
}
```

## Using an interface #

In the example above the `Twitter` class is mocked in the most easy way: by extending the class. If you prefer, you could also use an interface.

First, create an interace containing that `tweet` method.

```php
namespace App\Services\Twitter\Contracts;

interface Twitter 
{
    public function tweet(string $status)
}
```

You should let the actual `Twitter` class implement the interface.

```php
namespace App\Services\Twitter;

use App\Services\Twitter\Contracts\Twitter as TwitterInterface;

class Twitter implements TwitterInterface
{
   // ...
}
```

Your mock shouldn't extend the `Twitter` class anymore but implement the interface. You don't need to add that empty constructor anymore.

```php
namespace Tests\Mocks;

use App\Services\Twitter\Contracts\Twitter as TwitterInterface;

class Twitter implements TwitterInterface
{
   // ...
}
```

In the service provider you should use that interface to bind the concrete class.

```php
namespace App\Services\Twitter;

use Illuminate\Support\ServiceProvider;
use App\Services\Twitter\Contracts\Twitter as TwitterInterface;

class TwitterServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->bind(TwitterInterface::class, function () {
            $connection = // ...
            return new Twitter($connection);
        });
    }
}
```

And of course you should also use that interface in the `fakeTwitter` function in your tests.

```php
namespace Tests;

use App\Services\Twitter\Twitter;

use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Tests\Mocks\Twitter as TwitterMock;
use App\Services\Twitter\Contracts\Twitter;

abstract class TestCase extends BaseTestCase
{
    // ...

    protected function fakeTwitter(): TwitterMock
    {
        return $this->swap(Twitter::class, new TwitterMock());
    }
}
```

The big benefit of using an interface is that the actual Twitter class and the mocked class are guaranteed to have the right methods.

If you should use this is up to you. Do you like the simplicity of just extending a class? Do that, but be aware that you should keep your class and mock in sync manually.

## In conclusion #

Of course this is just one of the many possibilities to test this kind of code. Want to use an interface this, perfect. Want to use a regular mock, that’s good too (benefit: you don’t have to maintain your custom mock class).

What are your thoughts on this? Let me know in the comments below!

A big thank you to [Frederick Vanbrabant](https://twitter.com/maybefrederick) and [Brent Roose](https://twitter.com/brendt_gd) for reviewing this post.