Measuring class parameter returning mock

I am new to unit testing and am trying to test controller method in Laravel 5.1 and Mockery.

I am trying to test the method registerEmail

I wrote below:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Response;
use Mailchimp;
use Validator;

/**
 * Class ApiController
 * @package App\Http\Controllers
 */
class ApiController extends Controller
{

    protected $mailchimpListId = null;
    protected $mailchimp = null;

    public function __construct(Mailchimp $mailchimp)
    {
        $this->mailchimp = $mailchimp;
        $this->mailchimpListId = env('MAILCHIMP_LIST_ID');
    }

    /**
     * @param Request $request
     * @return \Illuminate\Http\JsonResponse
     */
    public function registerEmail(Request $request)
    {

        $this->validate($request, [
            'email' => 'required|email',
        ]);

        $email  = $request->get('email');

        try {
            $subscribed = $this->mailchimp->lists->subscribe($this->mailchimpListId, [ 'email' => $email ]);
            //var_dump($subscribed);
        } catch (\Mailchimp_List_AlreadySubscribed $e) {
            return Response::json([ 'mailchimpListAlreadySubscribed' => $e->getMessage() ], 422);
        } catch (\Mailchimp_Error $e) {
            return Response::json([ 'mailchimpError' => $e->getMessage() ], 422);
        }

        return Response::json([ 'success' => true ]);
    }
}

      

I am trying to poke fun at the Mailchimp object to work in this situation. So far, my test looks like this:

<?php

use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class HomeRouteTest extends TestCase
{

    use WithoutMiddleware;

    public function testMailchimpReturnsDuplicate() {
        $listMock = Mockery::mock('Mailchimp_Lists')
            ->shouldReceive('subscribe')
            ->once()
            ->andThrow(\Mailchimp_List_AlreadySubscribed::class);

        $mailchimp = Mockery::mock('Mailchimp')->lists = $listMock;

        $this->post('/api/register-email', ['email'=>'duplicate@email.com'])->assertJson(
            '{"mailchimpListAlreadySubscribed": "duplicate@email.com is already subscribed to the list."}'
        );
    }
}

      

I have phpUnit returning a failed test.

HomeRouteTest :: testMailchimpReturnsDuplicate Mockery \ Exception \ InvalidCountException: The subscribe () method from Mockery_0_Mailchimp_Lists should be called exactly 1 time, but it is called 0 times.

Also, if I assert that the status code is 422, phpUnit reports that it is receiving a 200 status code.

It works great when I check it manually, but I think I am missing something quite simple.

+3


source to share


2 answers


I managed to solve this myself. I ended up moving the subscription to a separate Job class and was able to test what would override the Mailchimp class in the test file.

class Mailchimp {
    public $lists;

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

class Mailchimp_List_AlreadySubscribed extends Exception {}

      



And one test

public function testSubscribeToMailchimp() {
    // create job
    $subscriber = factory(App\Models\Subscriber::class)->create();
    $job = new App\Jobs\SubscribeToList($subscriber);

    // set up Mailchimp mock
    $lists = Mockery::mock()
        ->shouldReceive('subscribe')
        ->once()
        ->andReturn(true)
        ->getMock();

    $mailchimp = new Mailchimp($lists);

    // handle job
    $job->handle($mailchimp);

    // subscriber should be marked subscribed
    $this->assertTrue($subscriber->subscribed);
}

      

0


source


Mockery expects the class passed to the controller to be a mock object, as you can see here in the docs :

class Temperature
{
    public function __construct($service)
    {
        $this->_service = $service;
    }
}

      

Unit Test

$service = m::mock('service');
$service->shouldReceive('readTemp')->times(3)->andReturn(10, 12, 14);

$temperature = new Temperature($service);

      

In laravel IoC, it automatically loads classes and injects them, but since its class is not loaded Mailchimp_Lists

, it will not be a mock object. Mailchimp requires a class on top of the main classrequire_once 'Mailchimp/Lists.php';



Then Mailchimp then automatically loads the class into the constructor

$this->lists = new Mailchimp_Lists($this);

      

I don't think you can mock this class very easily out of the box. Since it is not necessary to go through the mock object of the Mailchimp class and replace it with an instance of the realMailchimp_Lists

I see that you are trying to overwrite a list member variable with a new Mock before calling the controller. Are you sure the list object is being replaced by you by mocking it? Try to see what classes are in the controller when it loads and see if it actually gets canceled.

0


source







All Articles