How to use Mockery for a class used in a controller?

Published 1 week ago by shadrix

Let's say my controller looks like that.

public function store()
{
    Newsletter::subscribePending($email);
}

And Newsletter is a Class to use MailChimps Api easier.

Now how do I write a test for it?

I tried this, but it does not work. (I'm new to Mockery and it sends the data to mailchimp).

use Spatie\Newsletter\Newsletter;
class NewsletterTest extends TestCase {

    use RefreshDatabase;

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

        \Mockery::mock(Newsletter::class);
    }

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


    /** @test */
    public function a_guest_can_subscribe_to_newsletter() 
    {
        $this->withoutExceptionHandling();
        $this->json('post', route('newsletter.store'), ['email' => '[email protected]'])
             ->assertStatus(201);
    }
}
Best Answer (As Selected By shadrix)
Talinon

I think you're under the impression that Mockery wields some magic where it can alter your source code with a mock. This is not the case - your test post will still execute your source code exactly as you have it.

Instead, in your test, you can swap out the underlying Newsletter class by calling swap() on the facade.

use Spatie\Newsletter\Newsletter;
class NewsletterTest extends TestCase {

    use RefreshDatabase;

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

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


    /** @test */
    public function a_guest_can_subscribe_to_newsletter() 
    {

    $mock = \Mockery::mock(Newsletter::class)->shouldReceive('subscribePending')->once();  

    \Newsletter::swap($mock);

        $this->json('post', route('newsletter.store'), ['email' => '[email protected]'])
             ->assertStatus(201);
    }
}

The above test will swap your underlying Newsletter class with a mock (for the duration of the test), that expects the function subscribePending to be called exactly once. If the function doesn't get called, Mockery will throw Mockery\Exception\InvalidCountException

Keep in mind, this does not test anything, other than the fact that at some point in your stack, Newsletter::subscribePending() gets called.

You could do the same with a service provider, or anything bound in the Laravel Service Container.

    $mock = Mockery::mock('someClass'); // chain on method calls
    app()->instance('someClass', $mock); // bind the mock object into the Service Container

Now whenever 'someClass' gets resolved out of the Container, it will be your mock, not your source object.

Hope this helps you better understand how Mockery works.

shadrix
shadrix
1 week ago (33,210 XP)

Ah I see I need to use something like that

 $this->mock = \Mockery::mock(Newsletter::class);

 $this->app->instance(Newsletter::class, $this->mock);

I'm getting on a right track.

Talinon
Talinon
1 week ago (67,440 XP)

I think you're under the impression that Mockery wields some magic where it can alter your source code with a mock. This is not the case - your test post will still execute your source code exactly as you have it.

Instead, in your test, you can swap out the underlying Newsletter class by calling swap() on the facade.

use Spatie\Newsletter\Newsletter;
class NewsletterTest extends TestCase {

    use RefreshDatabase;

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

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


    /** @test */
    public function a_guest_can_subscribe_to_newsletter() 
    {

    $mock = \Mockery::mock(Newsletter::class)->shouldReceive('subscribePending')->once();  

    \Newsletter::swap($mock);

        $this->json('post', route('newsletter.store'), ['email' => '[email protected]'])
             ->assertStatus(201);
    }
}

The above test will swap your underlying Newsletter class with a mock (for the duration of the test), that expects the function subscribePending to be called exactly once. If the function doesn't get called, Mockery will throw Mockery\Exception\InvalidCountException

Keep in mind, this does not test anything, other than the fact that at some point in your stack, Newsletter::subscribePending() gets called.

You could do the same with a service provider, or anything bound in the Laravel Service Container.

    $mock = Mockery::mock('someClass'); // chain on method calls
    app()->instance('someClass', $mock); // bind the mock object into the Service Container

Now whenever 'someClass' gets resolved out of the Container, it will be your mock, not your source object.

Hope this helps you better understand how Mockery works.

shadrix
shadrix
1 week ago (33,210 XP)

@Talinon thanks! It helps me a lot. Mockery is super weird to understand, especially because I didn't need it until now. Thank you again!

Please sign in or create an account to participate in this conversation.