Параметр фиктивного класса, который возвращает макет

Я новичок в модульном тестировании и пытаюсь протестировать метод контроллера в Laravel 5.1 и Mockery.

Я пытаюсь протестировать метод registerEmail, который я написал ниже:

<?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 ]);
    }
}

Я пытаюсь смоделировать объект Mailchimp, чтобы он работал в этой ситуации. Пока мой тест выглядит следующим образом:

<?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'=>'[email protected]'])->assertJson(
            '{"mailchimpListAlreadySubscribed": "[email protected] is already subscribed to the list."}'
        );
    }
}

У меня phpUnit возвращает неудачный тест.

HomeRouteTest::testMailchimpReturnsDuplicate Mockery\Exception\InvalidCountException: метод subscribe() из Mockery_0_Mailchimp_Lists должен вызываться ровно 1 раз, но вызываться 0 раз.

Кроме того, если я утверждаю, что код состояния равен 422, phpUnit сообщает, что он получает код состояния 200.

Он отлично работает, когда я тестирую его вручную, но мне кажется, что я упускаю из виду что-то довольно простое.


person Lea    schedule 30.07.2015    source источник


Ответы (2)


Мне удалось решить это самостоятельно. В конце концов я переместил подписку в отдельный класс Job и смог проверить, что это переопределение класса Mailchimp в тестовом файле.

class Mailchimp {
    public $lists;

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

class Mailchimp_List_AlreadySubscribed extends Exception {}

И один тест

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);
}
person Lea    schedule 09.08.2015

Mockery будет ожидать, что класс, передаваемый контроллеру, будет фиктивным объектом, как вы можете видеть здесь в их документы:

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

Модульный тест

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

$temperature = new Temperature($service);

В laravel IoC он автоматически загружает классы и внедряет их, но поскольку это не автозагружающий класс Mailchimp_Lists, он не будет фиктивным объектом. Mailchimp требует класс поверх своего основного класса require_once 'Mailchimp/Lists.php';

Затем Mailchimp автоматически загружает класс в конструктор.

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

Я не думаю, что вы сможете легко издеваться над этим классом из коробки. Поскольку нет возможности передать фиктивный объект классу Mailchimp и заменить им экземпляр реального Mailchimp_Lists

Я вижу, вы пытаетесь перезаписать переменную-член lists новым макетом, прежде чем вызывать контроллер. Вы уверены, что объект списков заменяется на издевательский? Попробуйте посмотреть, какие классы находятся в контроллере, когда он загружается, и посмотрите, действительно ли он переопределяется.

person brenjt    schedule 03.08.2015
comment
Мне удалось убедиться, что контроллер не получает фиктивную версию класса Mailchimp. Большая разница между моим тестом и вашим примером заключается в том, что я использую функции интеграционного теста для тестирования контроллера, а вы создаете экземпляр класса. - person Lea; 06.08.2015