尝试使用 phpunit 模拟控制器内部的 http 客户端,但它不起作用

Posted

技术标签:

【中文标题】尝试使用 phpunit 模拟控制器内部的 http 客户端,但它不起作用【英文标题】:Trying to mock an http client that is inside a controller, using phpunit, but it doesn't work 【发布时间】:2021-12-30 07:23:07 【问题描述】:

我必须测试使用 Guzzle Wrapper 作为客户端从 API 获取数据的控制器的路由。该应用程序使用 Laravel 作为框架。

这是我目前正在测试的控制器功能中给我带来麻烦的代码部分:

public function addContacts(Request $request)

   ...
   $client = new GuzzleWrapper();
   $response = $client->post($uri, $data);
   if($response->getStatusCode() != 200)  
      return response()->json("Problem getting data", 500);
   
   ...

现在,我尝试的是 getMockBuilder:

$mock = getMockBuilder(GuzzleWrapper::class)->onlyMethods(array('post'))->getMock();
$mock->expects($this->once())->method('post')->willReturn(response()->json([$responseData], 200));

http::fake:

Http::fake(
   [$uri => Http::response([$responseData], 200)
);

嘲讽:

$mockGuzzleClient = Mockery::mock(GuzzleWrapper::class);
$mockGuzzleClient->shouldReceive('post')
->andReturn(response()->json([$responseData], 200));

我也尝试过这样的嘲讽:

$mockGuzzleClient = Mockery::mock(GuzzleWrapper::class, function (MockInterface $mock)
   $mock->shouldReceive('post')
   ->andReturn(response()->json([$responseData], 200));
);

像这样:

$this->app->instance(
   GuzzleWrapper::class,
   Mockery::mock(GuzzleWrapper::class, function (MockInterface $mock)
      $mock->shouldReceive('post')
      ->andReturn(response()->json([$responseData], 200));
   )
);

在我尝试的所有操作之后,调用测试我的控制器的功能:

//Successfully add contacts to list
$this->json('POST', $addContactUri, $input_data, $token);
$this->seeStatusCode(201);

现在!无论我尝试什么,就好像 GuzzleWrapper 从未被嘲笑过,它仍然会发布并且不返回状态代码 200。无论我在谷歌上找到什么,它都不适合这种情况......有人可以帮助我吗?

【问题讨论】:

你是在使用 Laravel 还是任何其他框架,还是这个原生 php 我正在使用 Laravel,我还使用 Mockery 在同一个测试中模拟另一个客户端,它工作得很好......虽然我不必为那个做一个 post 函数的期望 尝试只使用Http::fake,但尝试硬编码的URL,如example.com,并在真实代码上使用相同的URL,看看会发生什么。你得到的真正输出是什么,因此你知道它不起作用? 【参考方案1】:

Mocking 是基于容器的,为了让Laravel 获取你的模拟类,你永远不应该使用new 关键字。而是使用 resolve() 来使用容器。

$client = resolve(GuzzleWrapper::class);

这应该适用于您使用Mockery::mock() 的以下模拟方法之一。但是你模拟响应的方式,没有看到 GuzzleWrapper,我不希望它返回 Laravel 响应,否则它是自定义代码。

$mockGuzzleClient = Mockery::mock(GuzzleWrapper::class, function (MockInterface $mock)
   $mock->shouldReceive('post')
        ->andReturn(response()->json([$responseData], 200));
);

最正确的方法是使用Http 门面。您在控制器中的调用应如下所示。

Http::post($uri, $data);

模拟应该是这样的,一般来说,您似乎在模拟尝试中将 guzzle 和 Http 可互换的组合,并且不会飞。如果您使用 Http 模拟,请使用 Http 外观。

Http::fake([
    $uri => Http::response(['your data' => 'response'], 200, []),
]);

【讨论】:

我不是编写控制器代码的人,我认为我不能更改它...有什么办法可以在不更改控制器代码的情况下做到这一点?跨度> 不,因为你不能模拟使用 new 关键字的类但是 $client = resolve(GuzzleWrapper::class);等同于 $client = new GuzzleWrapper();如果您没有更改绑定 但是模拟需要解析

以上是关于尝试使用 phpunit 模拟控制器内部的 http 客户端,但它不起作用的主要内容,如果未能解决你的问题,请参考以下文章

PHPUnit:如何通过测试方法模拟内部调用的函数

试图模拟一个类,使其不会在 phpUnit 中执行构造函数

在 PHPUnit 中调用路由时如何在 Laravel 8 中模拟 Eloquent 模型

Laravel PHPUnit 模拟请求

PHPUnit 模拟对象和方法类型提示

PHPUnit:使用注解返回特定类型真的有必要吗?