返回模拟的mock类参数


Mocking class parameter that returns a mock

我是单元测试的新手,并试图在Laravel 5.1和mock中测试控制器方法。

我正在尝试测试我写的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'=>'duplicate@email.com'])->assertJson(
            '{"mailchimpListAlreadySubscribed": "duplicate@email.com is already subscribed to the list."}'
        );
    }
}

我有phpUnit返回一个失败的测试。

HomeRouteTest:: testMailchimpReturnsDuplicatemock 'Exception'InvalidCountException:方法subscribe()从Mockery_0_Mailchimp_Lists应该被调用恰好1次,但调用0次。

同样,如果我断言状态码是422,phpUnit报告它正在接收状态码200。

当我手动测试它时,它工作得很好,但我想我忽略了一些相当容易的东西。

我自己解决了。我最终将订阅移动到一个单独的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);
}

mock将期望传递给控制器的类是一个mock对象,您可以在他们的文档中看到:

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的实例

我看到你试图在调用控制器之前用一个新的Mock覆盖列表成员变量。您确定列表对象已被替换为您所模拟的对象吗?试着看看当它被加载时控制器中的类是什么,看看它是否实际上被覆盖了。