Prophecy - Expect that no method will be called on an object - php

right now I'm using the following code in PHPUnit to expect that no method is called on a mock:
$object = $this->createMock(ClassA:class);
$object->expects($this->never())->method($this->anything());
So far I haven't found a way to achieve the same result in Prophecy. I only have been able to test assumptions on specific methods so far and not on all methods like in the sample above.
Currently I'm using the following custom assertion to test whether no method has been called. Prophecy's ObjectProphecy exposes a method to get all calls to a specific function so I'm using reflection to get all methods on a class and than each call for each method. If the array of calls is empty afterwards, I know that no method has been called. The method now looks like this:
public function assertNoMethodHasBeenCalled(ObjectProphecy $prophecy, string $className)
{
$methods = get_class_methods($className);
$calls = [];
foreach ($methods as $method) {
$reflectionMethod = new \ReflectionMethod($className, $method);
$numArgs = count($reflectionMethod->getParameters());
$calls = array_merge(
$calls,
$prophecy->findProphecyMethodCalls(
$method,
new ArgumentsWildcard(array_fill(0, $numArgs, Argument::any()))
)
);
}
$this->assertEmpty($calls);
}
It's working for my limited sample size so far but I'm not happy with it. I feel like there should be an easier way to achieve the same result.

To my knowledge, there is no simple to achieve this with Prophecy.
Prophecy works differently from PHPUnit's mocks when it comes to expectations. As soon as you set up an expectation, every call to your mock that isn't a prediction or a promise, will fail the test. Given the following object prophecy:
$prophecy = $this->prophesize(ClassA::class);
$prophecy->methodA()->shouldNotBeCalled();
$objectA = $prophecy->reveal();
Both
$objectA->methodA();
and
$objectA->methodB();
will fail the test.
So, when you don't set up any expectations, you have to check for calls manually like you did.

Related

PHPUnit: How to test a class method that uses another static classes method

I have a class method I am trying to write a unit test for, it starts off something like this:
public function determineStatusActivity($proposal, $decisionStatusId): ?array
{
if (isset($proposal->contact_org_id)) {
$this->personId = PipedriveActivityHelper::getPersonId($proposal->contact_org_id);
}
I then have my test method which will need the response to run the check:
public function acceptedActivityTest()
{
$activity = new CreateActivityInPipedriveForProposalStatusChange();
$response = $activity->determineStatusActivity($this->proposal, 1);
//Compare the response to a defined stub..
}
This class method determineStatusActivity() receives two pieces of data that I can create mocks for a pass to it, however I am at a loss at what to do when it gets to the static PipedriveActivityHelper class, is there a way to define the static class' behaviour in the test file method?
I believe this is what you are looking for, notice the test class decorators. TDD is always the best approach.

Is it possible to mock a method chained through an attribute?

I have the following PHP function, for which I am trying to write a PHPUnit test:
public function getDisplayMode()
{
if($this->request->query->get('master_video'))
{
return 'listTranslations';
}
else
{
return 'default';
}
}
The line $this->request->query->get('master_video') is what I am having trouble with.
I have mocked up the request object, so if it were just $this->request->get('master_video'), I could easily use the method method to tell my system what should be returned by get.
But I don't know how to do that when the query property is assumed to be present. (I don't know of any property method on my mock object, for example.)
Is there a way to define what should be returned by the get method here?
Create a mock of query and specify what happens on ->get(). The test would end up looking something like this:
public function testGetDisplayMode() {
$mockQuery = $this->getMockBuilder('Query') // Or whatever the query class is
->setMethods(['get'])
->getMock();
$mockQuery->expects($this->once())
->method('get')
->with('master_video')
->willReturn('foo');
$mockRequest = $this->getMockBuilder('Request') // Or whatever the request is
->getMock();
$mockRequest->query = $mockQuery;
$sut = new Bar($mockRequest) // Or however you instantiate the class with the mock request.
$mode = $sut->getDisplayMode();
// Do your assertions on the returned mode here.
}
As a general rule, I have found when I am doing things like this where I have a mock object returning a mock object that it is a code smell. Your method here doesn't need the $this->request it needs the query object. You should be giving this directly to the object or passing it into this method.
Without knowing more about the class that you are making, I can't give more advice.
If something is hard to write a test for, it is a sign that you have not optimally designed your code.

Return arguments being sent to a Mockery mocked function?

I'm rather new to the PHP Mockery framework, and attempting to test multiple functions on a mocked service request function. The different functions send the same variable names with different values. I'd like to test to make sure each case is hitting the mocked class. Can I receive multiple arguments with my mocked function and return each of them to the tester function?
The function to test:
public function doSomething() {
$result = $this->serviceRequest('stuff', '/url', []);
return $result;
}
The pseudocode for testing function:
public function testDoSomething() {
$service_mock = $this->mockService();
$this->assertEquals($service_mock->doSomething(), $returnedArgs);
}
I'd ideally like to take those three arguments being sent to the mock, return them with the mock's andReturn() or something similar to be asserted in the original test method (to verify they are being sent to the mock). Is there a good way to do this or a common practice I'm missing?
Right now, my mock is looking similar to this pseudocode (and those 3 arguments are being validated in closure $args):
$client->shouldReceive('serviceRequest')->withArgs($args)->once()->andReturnUsing($args)->getMock();
Thanks!
You can use a callback to do this:
The below example is adapted form the Mockery docs, although it uses it as an example for reference passing, it can be used for any custom expectations.
$m->shouldReceive('insert')->with(
\Mockery::on(function(&$data) use($logger) {
if (!is_array($data)) return false;
$data['_id'] = 123;
$logger->logParameters('insert', $data);
return true;
}),
\Mockery::any()
);
http://docs.mockery.io/en/latest/reference/pass_by_reference_behaviours.htm

Stuck on writing PHPUnit getMock unit test

I'm admittedly very rusty with unit testing.
I have the following function in my Registration.php:
protected function _showModal($request) {
if (Store::isUserSupported()) {
return false;
}
return true;
}
I started writing the following test but I know I'm missing some key items:
public function testUserSupported() {
$mockStore = $this->getMockClass('Store', array('isUserSupported'));
$mockStore::staticExpects($this->once())
->method('isUserSupported')
->will($this->returnValue(true));
$mockStore::isUserSupported();
$plugin = $this->getMockBuilder('Registration')
->setMethods(array('_showModal'))
->disableOriginalConstructor()
->getMock();
$plugin = $this->getPublicClass($plugin);
$plugin->expects($this->once())
->method('_showTermsModal')
->will($this->returnValue(true));
}
The $mockStore part is getting called, but not sure how to tie it into my $plugin call.. I want to write a test to mock Store::isUserSupported() returning true within the showModal function. Any words of advice?
You should avoid static calls in your code, because they make your code coupled together and hard to change and hard to maintain. It also makes it harder to tests, as you are suffering here.
Instead of static calls, pass the collaborators needed to work. In your code example, the class you want to test would receive as parameter the Store class, instead of calling it statically.
By doing this change, you can now create a mock for the method isUserSupported() of the Store class, and pass the mock to the object under test, that will efectively use the mocked object.
$mockStore = $this->getMock('Store', array('isUserSupported'));
$mockStore->expects($this->once())
->method('isUserSupported')
->will($this->returnValue(true));
$object_under_test = new Registration( $mockStore );
$object_under_test->showModal($request); // Only in case showModal is public, otherwise call public method
If the Store class is only needed for that method, and you don't want to pass it as dependency in the constructor, you can pass it on the method itself.
$mockStore = $this->getMock('Store', array('isUserSupported'));
$mockStore->expects($this->once())
->method('isUserSupported')
->will($this->returnValue(true));
$object_under_test = new Registration();
$object_under_test->showModal($mockStore, $request); // Only in case showModal is public, otherwise call public method
Also, you shouldn't test protected/private methods of your classes, since they are low level implementation details that your tests don't need to know about. You should only make calls to public methods in your tests. Otherwise, the tests become very coupled with the real implementation, and if you refactor your code, you will most likely have to change the tests too.

Testing constructor args in phpunit

We use Varien_Http_Client to make http requests from a Magento extension, like this:
public function callApi(…)
{
<SNIP>
// Set default factory adapter to socket in case curl isn't installed.
$client = new Varien_Http_Client($apiUri, array(
'adapter' => 'Zend_Http_Client_Adapter_Socket',
'timeout' => $timeout,
));
$client->setRawData($xmlDoc->saveXML())->setEncType('text/xml');
$response = $client->request($method);
$results = '';
if ($response->isSuccessful()) {
$results = $response->getBody();
}
return $results;
}
I understand I should avoid testing the internals of Varien_Http_Client; rather, I should test that we are sending it the right inputs, and handling its outputs correctly. I can mock Varien_Http_Client easily enough, but even if I refactor this code to let me replace the Varien_Http_Client with its mock, I don't understand how to generally* test that the constructor was called with the expected arguments, since the constructor is called by PHPUnit::getMock.
I don't need a mock object; I need a mock class. How can I test that a constructor was called with expected arguments?
* (In this case I know ways to work around this problem specific to Varien_Http_Client, but what can I do with more opaque third-party code?)
This is what we call "untestable" code. When you build dependencies inside your methods, there is no way to mock them. Every use of "new" keyword in your model is a signal that you should consider injecting the object instead of create it inside. In my opinion, the only exception from that rule is when you create a "data container" object or factory class. But in these cases probably you can test the object because methods will return it.
So as you said, the method you showed need a little refactor, for example:
class ApiClass
{
protected $client;
public function __construct(Varien_Http_Client $client)
{
$this->client = $client;
}
public function callApi()
{
$this->client->setRawData($xmlDoc->saveXML())->setEncType('text/xml');
(...)
Best!

Categories