【问题标题】:Possible to mock model method output in Laravel / PHPUnit?可以在 Laravel / PHPUnit 中模拟模型方法输出吗?
【发布时间】:2019-11-04 02:45:50
【问题描述】:

是否可以在 Laravel 5.8 中模拟或伪造模型方法的输出?

例如,考虑这个模型

class Website extends Model
 {
     public function checkDomainConfiguration($domain): bool
     {
         try {
             $records = dns_get_record($domain, DNS_A);
         } catch (ErrorException $e) {
             return false;
         }

         if (isset($records[0]['ip']) && $records[0]['ip'] === $this->server->ipv4_address) {
             return true;
         }

         return false;
     }
 }

为了测试的目的,我需要告诉 phpunit 当这个方法触发时(它在控制器中被调用),如果我故意让它失败,则返回 true 或 false。在测试中我工厂了一个网站,当然它会失败 php 方法dns_get_record

我已经阅读了 Laravel 文档,并在 google 上搜索了有关模拟模型方法的信息,但似乎找不到任何东西,除了在检查我是否未处于测试模式的方法周围包裹一个大的 if 之外,如果我只是返回 true。

更新 这是我如何在控制器中调用方法的示例

class SomeController extends Controller
{
    public function store(Website $website, Domain $domain)
    {
        if (! $website->checkDomainConfiguration($domain->domain)) {
            return response([
                'error' => 'error message'
            ], 500);
        }

        // continue on here if all good.
    }
}

这是测试中的一些代码

$website = factory(Website::class)->create();
$domain = factory(Domain::class)->create([
    'website_id' => $website->id
]);
//Mock the website object
$websiteMock = \Mockery::mock(Website::class)->makePartial();
$websiteMock->shouldReceive('getAttribute')
    ->once()
    ->with('domain')
    ->andReturn($website->domain);

$websiteMock->shouldReceive('checkDomainConfiguration')
    ->with($domain->domain)
    ->andReturn(true);

app()->instance(Website::class, $websiteMock);

// tried end point like this
    $response = $this->json(
        'POST',
        'api/my-end-point/websites/' . $website->domain . '/domain/' . $domain->id
    );
    //also tried like this
    $response = $this->json(
        'POST',
        'api/my-end-point/websites/' . $websiteMock->domain . '/domain/' . $domain->id
    );

控制器方法接受网站和域模型绑定。如果我在控制器顶部dd(get_class($website)),它会显示实际模型的命名空间,而不是模拟。

【问题讨论】:

  • 你能把代码贴在你实际调用该方法的地方吗?你可以这样做,但我需要它来编写更详细的回复。
  • @namelivia 查看我的问题中的更新。谢谢。

标签: php laravel testing mocking phpunit


【解决方案1】:

在您的代码中,您将模型作为参数注入控制器,这意味着 Laravel 的 ioc 将被要求提供 Website 模型的实例,并在执行实际代码时提供它。

在您的测试中,您可以创建一个模拟,然后告诉 ioc 在要求 Website 的实例时返回该模拟,例如:

$websiteMock = Mockery::mock(Website::class);
app()->instance(Website::class, $websitemock)

在执行控制器之前。现在,在您的测试中执行控制器时,模拟将是接收方法调用的那个,您可以将其设置为期望它们并伪造响应。特别是在您的代码上,您的模拟应该期望并伪造两个调用,例如:

$websiteMock->shouldReceive('getAttribute')
    ->once()
    ->with('domain')
    ->andReturn('test.domain'):
$websiteMock->shouldReceive('checkDomainConfiguration')
    ->once()
    ->with('test.domain')
    ->andReturn(false):

使用整个代码进行编辑(我已经编造了一些不重要的东西,例如路由或控制器名称,请使用您自己的)。

控制器

<?php

namespace App\Http\Controllers;

use App\Domain;
use App\Website;

class StackOverflowController extends Controller
{
    public function store(Website $website, Domain $domain)
    {
        if (! $website->checkDomainConfiguration($domain->domain)) {
            return response([
                'error' => 'error message'
            ], 500);
        }
    }
}

集成测试

public function testStackOverflow()
{
    $website = Mockery::mock(Website::class);
    app()->instance(Website::class, $website);
    $domain = Mockery::mock(Domain::class);
     app()->instance(Domain::class, $domain);

    $domain->shouldReceive('getAttribute')
        ->once()
        ->with('domain')
        ->andReturn('some.domain');

    $website->shouldReceive('checkDomainConfiguration')
        ->once()
        ->with('some.domain')
        ->andReturn(false);
    $response = $this->json('POST', '/stack/websiteId/overflow/domainId');
    $this->assertEquals('error message', json_decode($response->getContent())->error);
    $this->assertEquals(500, $response->status());
}

我也没有包含集成测试文件的开头,但请仔细检查您是否包含:

use App\Domain;
use App\Website;

【讨论】:

  • 嗨@namelivia,好的,是的,这很有意义。但是我已经按照你的建议实现了,并且方法调用在测试中仍然返回 false。
  • 我可以看看你的测试代码吗?尝试在控制器代码上添加dd(get_class($website)),然后执行测试以查看实例是否为模拟实例。
  • 我对问题做了一些修改。如果我在测试中dd($websiteMock-&gt;checkDomainConfiguration($domain-&gt;domain));,它返回true(好),但控制器仍然认为它有模型类,而不是模拟(坏)。
  • 好的,我明白了。所以嘲笑时我不能使用工厂?我应该使用模拟 -&gt;shouldReceive('getAttribute') 并返回值而不是模型上的所有属性?
  • 这取决于你,你也可以使用部分。
猜你喜欢
  • 2018-05-25
  • 2021-12-23
  • 2017-09-04
  • 2010-09-25
  • 2020-09-11
  • 2019-08-07
  • 2013-01-27
  • 2021-06-27
  • 2016-04-21
相关资源
最近更新 更多