假设正在测试一个类ApiClient
,如何断言storeToken()
中的retrieveNewToken()
是正确的,我的问题是我不能使用expects()
方法,因为$this->testedInstance
不是使用
ApiClient
private function retrieveNewToken()
{
$response = $this->client->post(
'login',
[
'json' => [
'username' => $this->apiLogin,
'password' => $this->apiPassword,
],
]
);
$statusCode = $response->getStatusCode();
$responseBody = json_decode($response->getBody());
if (Response::HTTP_OK === $statusCode) {
$this->storeToken($responseBody->token);
return $responseBody->token;
}
$exception = sprintf(
'Error retrieving token : %s',
$responseBody->message
);
$this->logger->error($exception);
throw new \Exception($exception);
}
private function storeToken($token)
{
$tokenCacheItem = $this->memcache->getItem('token');
$tokenCacheItem->set($token);
if (!$this->memcache->save($tokenCacheItem)) {
throw new \Exception('Error saving token');
}
}
现在我的测试是:
ApiClientTest
public function setUp()
{
$this->testedInstance = new ApiClient($dependencies);
}
public function tearDown()
{
$this->testedInstance = null;
}
public function retrieveNewTokenOK(){
$entityMock = $this->getMockBuilder(ApiClient::class)
->setConstructorArgs([
$dependencies;
])
->setMethods(['storeToken', 'prepareClient'])
->getMock();
$guzzleClientMock = $this->getMockBuilder(Client::class)
->setConstructorArgs([
[
'base_uri' => $this->baseUri,
'proxy' => ''
]
])->getMock();
$entityMock->expects($this->once())->method('prepareClient')->willReturn($guzzleClientMock);
$responseMock = $this->getMockBuilder(Response::class)->setConstructorArgs(['', Response::HTTP_OK])->getMock();
$responseMock->expects($this->once())->method('getStatusCode')->willReturn(Response::HTTP_OK);
$responseStreamMock = $this->createMock(StreamInterface::class);
$jsonDecode = $this->getFunctionMock(ApiClient::class, 'json_decode');
$jsonDecode->expects($this->any())->with($responseStreamMock)->willReturn((object) ['token' => 'JWTTOKEN']);
$entityMock->expects($this->once())->method('storeToken')->with('JWTTOKEN');
$this->invokeMethod($entityMock, 'retrieveNewToken');
}
答案 0 :(得分:0)
我了解您的挣扎。我去过很多次,我想可以为您提供帮助。通常,当某些代码难以测试时,只需要将其重组为较小的部分即可。我自由地重组了一些代码,使其更具可测试性。在这里您可以找到结果,我将对其进行详细说明。
生产代码:
<?php
declare(strict_types=1);
namespace App;
use GuzzleHttp\Client;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Response;
class ApiClient
{
/**
* @var string
*/
private $apiLogin;
/**
* @var string
*/
private $apiPassword;
/**
* @var Client
*/
private $client;
/**
* @var LoggerInterface
*/
private $logger;
/**
* @var TokenStorage
*/
private $tokenStorage;
public function __construct(
string $apiLogin,
string $apiPassword,
Client $client,
LoggerInterface $logger,
TokenStorage $tokenStorage
) {
$this->apiLogin = $apiLogin;
$this->apiPassword = $apiPassword;
$this->client = $client;
$this->logger = $logger;
$this->tokenStorage = $tokenStorage;
}
public function retrieveNewToken(): string
{
$response = $this->client->post('login', [
'json' => [
'username' => $this->apiLogin,
'password' => $this->apiPassword,
]
]);
$body = json_decode($response->getBody()->getContents());
if (Response::HTTP_OK !== $response->getStatusCode()) {
$errorMessage = sprintf('Error retrieving token: %s', $body->message);
$this->logger->error($errorMessage);
throw new \Exception($errorMessage);
}
$this->tokenStorage->storeToken($body->token);
return $body->token;
}
}
测试类:
<?php
declare(strict_types=1);
namespace App;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Response;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
class ApiClientTest extends TestCase
{
/**
* @var ApiClient
*/
private $apiClient;
/**
* @var Client|\PHPUnit_Framework_MockObject_MockObject
*/
private $client;
/**
* @var LoggerInterface|\PHPUnit_Framework_MockObject_MockObject
*/
private $logger;
/**
* @var TokenStorage|\PHPUnit_Framework_MockObject_MockObject
*/
private $tokenStorage;
protected function setUp(): void
{
$this->client = $this->createMock(Client::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->tokenStorage = $this->createMock(TokenStorage::class);
$this->apiClient = new ApiClient('test', 'test', $this->client, $this->logger, $this->tokenStorage);
}
public function testCanRetrieveNewToken(): void
{
$response = new Response(200, [], '{"token":"test-token"}');
$this->client->expects($this->once())
->method('post')
->willReturn($response);
$this->tokenStorage->expects($this->once())
->method('storeToken')
->with('test-token');
$token = $this->apiClient->retrieveNewToken();
self::assertEquals('test-token', $token);
}
/**
* @expectedException \Exception
* @expectedExceptionMessage Error retrieving token: Something went wrong
*/
public function testThrowsExceptionOnUnexpectedStatusCode(): void
{
$response = new Response(400, [], '{"message":"Something went wrong"}');
$this->client->expects($this->once())
->method('post')
->willReturn($response);
$this->apiClient->retrieveNewToken();
}
}
如您所见,我将令牌存储逻辑提取到了自己的类中。这是遵循“单一职责原则”的原则,该原则是创建只做事的小类。该决定还使ApiClient更具可测试性,因为您现在可以简单地断言,当检索到新令牌时,将使用正确的令牌调用TokenStorage类。 TokenStorage类的实现也将有其自己的测试,您可以在其中模拟Memcache类。
要注意的另一点是,我将函数的默认路径从引发Exception更改为返回新令牌。这与清理代码以及人们习惯于阅读代码有关。首次读取retrieveToken
方法时,很明显它将返回新令牌。
我希望这是有道理的。
干杯!