【问题标题】:Mocking class parameter that returns a mock返回模拟的模拟类参数
【发布时间】:2015-10-21 13:19:32
【问题描述】:

我是单元测试新手,并尝试在 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'=>'duplicate@email.com'])->assertJson(
            '{"mailchimpListAlreadySubscribed": "duplicate@email.com is already subscribed to the list."}'
        );
    }
}

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

HomeRouteTest::testMailchimpReturnsDuplicate Mockery\Exception\InvalidCountException:来自 Mockery_0_Mailchimp_Lists 的方法 subscribe() 应该被准确调用 1 次,但被调用 0 次。

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

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

【问题讨论】:

  • 嗨。您到Mailchimp 课程的完整路径是什么?不是别名,而是带有命名空间的完整路径。
  • 我在这里使用 skovmand/mailchimp-laravel 包:github.com/skovmand/mailchimp-laravel。 Mailchimp 类位于全局命名空间中,文件中还有 require_once 的许多其他类。
  • 你在嘲笑 Mailchimp_Lists,但我没有看到你的控制器中的任何地方都加载了该类。
  • 它不包含在控制器中,Mailchimp类文件需要_onces Mailchimp_Lists在里面。我认为通过模拟 Mailchimp 并将其上的列表参数设置为模拟,应该可以解决问题吗?显然还是有问题。
  • 也许是对这个问题的扩展:你将如何测试这个?

标签: php unit-testing mockery laravel-5.1


【解决方案1】:

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

【讨论】:

    【解决方案2】:

    Mockery 会期望传入控制器的类是一个模拟对象,正如您在他们的docs 中看到的那样:

    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 覆盖列表成员变量。您确定列表对象正在替换为您模拟的对象吗?尝试在控制器加载时查看控制器中的类,看看它是否真的被覆盖了。

    【讨论】:

    • 我能够确认控制器没有收到 Mailchimp 类的模拟版本。我的测试和您的示例之间的最大区别在于,我使用集成测试功能来测试控制器,而您正在实例化一个类。
    猜你喜欢
    • 2019-08-18
    • 2022-11-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多