Reputation: 4404
I want to test pretty specific piece of code, but I can't find a good way to do it. I have such code:
public function foo()
{
try {
//...some code
$this->service->connectUser();
} catch (\OAuth2Exception $e) {
$this->logger->error(
$e->getMessage(),
['exception' => $e]
);
}
}
And I want to test if the exception was thrown and logged to $this->logger. But I can't find a good way to do it. Here is how I do it currently.
public function testFoo()
{
$oauthException = new \OAuth2Exception('OAuth2Exception message');
//This is a $service Mock created with $this->getMockBuilder() in test case injected to AuthManager.
$this->service
->method('connectUser')
->will($this->throwException($oauthException));
//This is a $logger Mock created with $this->getMockBuilder() in test case injected to AuthManager.
$this->logger
->expects($this->once())
->method('error')
->with(
$this->isType('string'),
$this->logicalAnd(
$this->arrayHasKey('exception'),
$this->contains($oauthException)
)
);
//AuthManager is the class beeing tested.
$this->authManager->foo($this->token);
}
This will test if error
method was called with certain parameters, but array key 'exception'
and exception object can exist in different parts of the array. What I mean is that test will pass for such error method call:
$this->logger->error(
$e->getMessage(),
[
'exception' => 'someValue',
'someKey' => $e,
]
);
I would like to make sure that error
method will always receive such subset ['exception' => $e]
. Something like this would be perfect:
$this->logger
->expects($this->once())
->method('error')
->with(
$this->isType('string'),
$this->arrayHasSubset([
'exception' => $oauthException,
])
);
Is it possible to achieve with PHPUnit?
Upvotes: 1
Views: 1292
Reputation: 9582
You can use the callback()
constraint:
public function testFoo()
{
$exception = new \OAuth2Exception('OAuth2Exception message');
$this->service
->expects($this->once())
->method('connectUser')
->willThrowException($exception);
$this->logger
->expects($this->once())
->method('error')
->with(
$this->identicalTo($exception->getMessage()),
$this->logicalAnd(
$this->isType('array'),
$this->callback(function (array $context) use ($exception) {
$expected = [
'exception' => $exception,
];
$this->assertArraySubset($expected, $context);
return true;
})
)
);
$this->authManager->foo($this->token);
}
See https://phpunit.de/manual/current/en/test-doubles.html#test-doubles.mock-objects:
The
callback()
constraint can be used for more complex argument verification. This constraint takes a PHP callback as its only argument. The PHP callback will receive the argument to be verified as its only argument and should return true if the argument passes verification and false otherwise.
Also note how I adjusted setting up your test doubles:
connectUser()
to be invoked exactly once$this->willThrowException()
instead of $this->will($this->throwException())
$this->identicalTo($exception->getMessage())
instead of the more loose $this->isType('string')
I always try to make an argument to be as specific as possible, and only loosen constraints on intention.
Upvotes: 3
Reputation: 26
You can try PHPUnit spies as described in https://lyte.id.au/2014/03/01/spying-with-phpunit/
With spy you can do something like
$this->logger->expects($spy = $this->any())->method('error');
$invocations = $spy->getInvocations();
/**
* Now $invocations[0]->parameters contains arguments of first invocation.
*/
Upvotes: 0