I am attempting to test a web service interface class using PHPUnit. Basically, this class makes calls to a SoapClient object. I am attempting to test this class in PHPUnit using the getMockFromWsdl method described here:
http://www.phpunit.de/manual/current/en/test-doubles.html#test-doubles.stubbing-and-mocking-web-services
However, since I want to test multiple methods from this same class, every time I setup the object, I also have to setup the mock WSDL SoapClient object. This is causing a fatal error to be thrown:
Fatal error: Cannot redeclare class xxxx in C:\web\php5\PEAR\PHPUnit\Framework\TestCase.php(1227) : eval()'d code on line 15
How can I use the same mock object across multiple tests without having to regenerate it off the WSDL each time? That seems to be the problem.
--
Having been asked to post some code to look at, here's the setup method in the TestCase:
protected function setUp() {
parent::setUp();
$this->client = new Client();
$this->SoapClient = $this->getMockFromWsdl(
'service.wsdl'
);
$this->client->setClient($this->SoapClient);
}
This isn't an ideal solution, but in your setup give the SOAP mock a "random" class name, for example
$this->_soapClient = $this->getMockFromWsdl( 'some.wsdl', 'SoapClient' . md5( time().rand() ) );
This ensures that at least when the setup is called you don't get that error.
For basic usage, something like this would work. PHPUnit is doing some magic behind the scenes. If you cache the mock object, it won't be redeclared. Simply create a new copy from this cached instance and you should be good to go.
<?php
protected function setUp() {
parent::setUp();
static $soapStub = null; // cache the mock object here (or anywhere else)
if ($soapStub === null)
$soapStub = $this->getMockFromWsdl('service.wsdl');
$this->client = new Client;
$this->client->setClient(clone $soapStub); // clone creates a new copy
}
?>
Alternatively, you can probably cache the class's name with get_class and then create a new instance, rather than a copy. I'm not sure how much "magic" PHPUnit is doing for initialization, but it's worth a shot.
<?php
protected function setUp() {
parent::setUp();
static $soapStubClass = null; // cache the mock object class' name
if ($soapStubClass === null)
$soapStubClass = get_class($this->getMockFromWsdl('service.wsdl'));
$this->client = new Client;
$this->client->setClient(new $soapStubClass);
}
?>
Why are you creating the mock in setUp() if the point is to obtain mock class definition once per execution of whole test file? If I remember correctly it's run before each test defined in "this" test class... Try setUpBeforeClass()
From http://www.phpunit.de/manual/3.4/en/fixtures.html
In addition, the setUpBeforeClass() and tearDownAfterClass() template methods are called before the first test of the test case class is run and after the last test of the test case class is run, respectively.
Adding my $.02 here.. I recently ran across this same situation and after some frustration here is how I was able to solve it:
class MyTest extends PHPUnit_Framework_TestCase
protected static $_soapMock = null;
public function testDoWork_WillSucceed( )
{
$this->_worker->setClient( self::getSoapClient( $this ) );
$result = $this->_worker->doWork( );
$this->assertEquals( true, $result['success'] );
}
protected static function getSoapClient( $obj )
{
if( !self::$_soapMock ) {
self::$_soapMock = $obj->getMockFromWsdl(
'Test/wsdl.xml', 'SoapClient_MyWorker'
);
}
return self::$_soapMock;
}
}
I have many 'workers', each in their own test class and each of which needs access to a mocked SOAP object. In the second parameter to getMockFromWsdl I had to make sure I was passing each a unique name (e.g. SoapClient_MyWorker) or it would bring PHPUnit crashing down.
I am not sure whether or not getting the SOAP mock from a static function and getting access to the getMockFromWsdl function by passing in $this as a parameter is the best way to accomplish this, but there ya go.
One way to pass an object from test to test in PHPUnits is with test dependency, if instantiating a particular object is too taxing/time consuming:
<?php
/**
* Pass an object from test to test
*/
class WebSericeTest extends PHPUnit_Framework_TestCase
{
protected function setUp() {
parent::setUp();
// I don't know enough about your test cases, and do not know
// the implications of moving code out of your setup.
}
/**
* First Test which creates the SoapClient mock object.
*/
public function test1()
{
$this->client = new Client();
$soapClient = $this->getMockFromWsdl(
'service.wsdl'
);
$this->client->setClient($this->SoapClient);
$this->markTestIncomplete();
// To complete this test you could assert that the
// soap client is set in the client object. Or
// perform some other test of your choosing.
return $soapClient;
}
/**
* Second Test depends on web service mock object from the first test.
* #depends test1
*/
public function test1( $soapClient )
{
// you should now have the soap client returned from the first test.
return $soapClient;
}
/**
* Third Test depends on web service mock object from the first test.
* #depends test1
*/
public function test3( $soapClient )
{
// you should now have the soap client returned from the first test.
return $soapClient;
}
}
?>
PHPUnit creates a class for the mock based on the WSDL. The classname, if not provided, is constructed from the .wsdl filename, so it's always the same. Across tests, when it tries to create again the class, it crashes.
The only thing you need is add to the Mock definition a classname of your own, so PHPUnit does not create an automatic name, notice the second argument to $this->getMockFromWsdl:
protected function setUp() {
parent::setUp();
$this->client = new Client();
$this->SoapClient = $this->getMockFromWsdl(
'service.wsdl', 'MyMockClass'
);
$this->client->setClient($this->SoapClient);
}
You can now create as many clients as you want, only change the classname for each one.
Related
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.
Weirdly, not only have I failed to find an answer for this, but also this question doesn't seem to be a concern at all. It is for me, however.
How do you set an expectation of a parent method call with PHPUnit?
I already read the answers and discussions, the point of which is that "you don't need to test for that, you only test that it works". This is entirely wrong. Unit tests are there to test for the internals of the implementation. Testing that "it works" is for functional tests.
More context: I have an exception implementation, which accepts additional arguments to the constructor, and therefore naturally needs to call the parent::__construct() method, forwarding the message, the code, and the inner exception to it. I know that the parent method works, and I don't need to test it: it's a native PHP class, and even if it wasn't, that code is not in the class I am testing. So, all I need is to set some expectations, one of which is that the parent __construct() method is called with the appropriate args. Of course, the constructor is not the only case where this could be necessary.
Any ideas?
As for the PHPUnit and checking for parent methods calls it is unfortunately impossible. You can always create a mock of a class and check if the parent constructor was called but then you are only testing a mock and not your actual class. And this is definitely not what you want. As someone else already pointed out in another answer: you don't mock or stub subject-under-test (https://stackoverflow.com/a/6711948/4737924).
You are saying that you need to know is that the parent constructor was called. I think it is only a technical detail of the implementation. What you really need to know is if your exception behaves as it needs to behave.
Take a look at the following example:
class MyException extends Exception
{
private $somethingElse;
public function __construct(string $message, int $code, Throwable $previousException, SomethingElse $somethingElse)
{
$message = $message . ' - my additional message';
parent::__construct($message, $code, $previousException);
$this->somethingElse = $somethingElse;
}
public function getSomethingElse(): SomethingElse
{
return $this->somethingElse;
}
}
class MyExceptionTest extends TestCase
{
public function testIsCreatedProperly(): void
{
$previousException = new Exception();
$somethingElse = new SomethingElse();
$exception = new MyException('My message', 100, $previousException, $somethingElse);
$this->assertSame('My message - my additional message', $exception->getMessage());
$this->assertSame(100, $exception->getCode());
$this->assertSame($previousException, $exception->getPrevious());
$this->assertSame($somethingElse, $exception->getSomethingElse());
}
}
Here only the behavior matters. It is really irrelevant how it happened that the message is correct, or the code or the previous exception. All it matters is if it behaves as you wanted it to behave. You don't need to explicitly check if the parent constructor was called. You know it was called because the output is as expected.
You can create smth like the following trait, and use it in all classes, where you need to mock parent method call:
trait ParentCallTestable
{
/**
* Call parent methdod
* #param string $method Method name to call
* #return mixed
*/
public function parent($method, ...$args)
{
return parent::$method(...$args);
}
}
Then in a class, that uses this trait, standard parent call
$result = parent::fooMethod($arg1, $arg2);
is replaced with
$result = $this->parent('fooMethod', $arg1, $arg2);
and you can mock it easily.
I've looked around and I found a solution that works for normal objects, but it doesn't seem to work for mocks.
The test below fails with the message: Unable to set property someProperty of object type Mock_ClassToTest_40ea0b83: Property someProperty does not exist.
class sampleTestClass extends PHPUnit_Framework_TestCase
{
function test() {
$object = $this->getMockForAbstractClass(ClassToTest::class, [], '', false);
$this->setProtectedProperty($object, 'someProperty', 'value');
}
private function getReflectionProperty($object, $property) {
$reflection = new ReflectionClass($object);
$reflectionProperty = $reflection->getProperty($property);
$reflectionProperty->setAccessible(true);
return $reflectionProperty;
}
/**
* This method modifies the protected properties of any object.
* #param object $object The object to modify.
* #param string $property The name of the property to modify.
* #param mixed $value The value to set.
* #throws TestingException
*/
function setProtectedProperty(&$object, $property, $value) {
try {
$reflectionProperty = $this->getReflectionProperty($object, $property);
$reflectionProperty->setValue($object, $value);
}
catch ( Exception $e ) {
throw new TestingException("Unable to set property {$property} of object type " . get_class($object) .
': ' . $e->getMessage(), 0, $e);
}
}
}
abstract class ClassToTest
{
private $someProperty;
abstract function someFunc();
}
class TestingException extends Exception
{
}
EDIT: 8/31/2016 4:32 PM EST
Updated code in response to answer by Katie.
You are trying to call Reflection methods on a mocked object, instead, you can call it on the abstract class itself:
So change:
$reflection = new ReflectionClass(get_class($object));
to
$reflection = new ReflectionClass(ClassToTest::class);
And that will work for anything that is not abstract in the class, such as your property, or another method that is fully implemented.
Additional Note since OP was updated
The fix will still work for your first line in the getReflectionProperty. But if you don't have access to the class name, then that is a problem.
Using reflection to access protected and private properties and methods of classes in tests seems a very clever method but it leads to tests that are difficult to read and understand.
On the other hand, only the public interface of a class should be tested. Testing (and even caring about) the protected and private properties and methods of the tested class is a sign that the tests are written after the code. Such tests are brittle; any change in the implementation of the tested class breaks the tests, even when it doesn't break the functionality of the class.
There is usually no need to test an abstract class. Most of the times the tests of its children classes cover the relevant code of the abstract class too. If they doesn't cover some part of it then either that code is not needed there or the test cases do not cover all the corner cases.
However, sometimes one needs to write a testcase for an abstract class. The best approach, in my opinion, is to extend the abstract class at the bottom of the file that contains the test case, provide simple implementations for all its abstract methods and use this class as SUT.
Something along these lines:
class sampleTestClass extends PHPUnit_Framework_TestCase
{
public function testSomething()
{
$object = new ConcreteImplementation();
$result = $object->method1();
self::assertTrue($result);
}
}
class ConcreteImplementation extends AbstractClassToTest
{
public function someFunc()
{
// provide the minimum implementation that makes it work
}
}
You are testing a mock in the code you posted. The mocks are not meant to be tested. Their purpose is to simulate the behaviour of the collaborators of the SUT that are not appropriate to be instantiated in a test.
The reasons why a collaborator class is mocked in a test include, but are not limited to:
difficult creation; for example, when the constructor of the mocked class requires many arguments or other objects;
the collaborator is an abstract class or an interface; the actual implementation might not even exist when the test and the tested class are written;
the code of the collaborator takes a lot of time to complete or requires additional resources (disk space, database connection, Internet connection etc);
the code of the collaborator has permanent side effects; this is usually coupled with the previous reason.
I am new to using Mockery and confused with the terminology alias and overload. Can anyone please explain to me when to use which?
Overload is used to create an "instance mock". This will "intercept" when a new instance of a class is created and the mock will be used instead. For example if this code is to be tested:
class ClassToTest {
public function methodToTest()
{
$myClass = new MyClass();
$result = $myClass->someMethod();
return $result;
}
}
You would create an instance mock using overload and define the expectations like this:
public function testMethodToTest()
{
$mock = Mockery::mock('overload:MyClass');
$mock->shouldreceive('someMethod')->andReturn('someResult');
$classToTest = new ClassToTest();
$result = $classToTest->methodToTest();
$this->assertEquals('someResult', $result);
}
Alias is used to mock public static methods. For example if this code is to be tested:
class ClassToTest {
public function methodToTest()
{
return MyClass::someStaticMethod();
}
}
You would create an alias mock using alias and define the expectations like this:
public function testNewMethodToTest()
{
$mock = Mockery::mock('alias:MyClass');
$mock->shouldreceive('someStaticMethod')->andReturn('someResult');
$classToTest = new ClassToTest();
$result = $classToTest->methodToTest();
$this->assertEquals('someResult', $result);
}
Additional info to Fredrik Schöld's answer:
Alias mocking is persistent per test cases, so you need to deactivate it each time you use it. Just add this phpDoc to each test class there you use alias mocking:
/**
* #runTestsInSeparateProcesses
* #preserveGlobalState disabled
*/
I found this:
https://github.com/padraic/mockery-docs/blob/master/reference/startup_methods.rst
$mock = \Mockery::mock('alias:MyNamespace\MyClass');
Prefixing the valid name of a class (which is NOT currently loaded) with "alias:" will generate an "alias mock". Alias mocks create a class alias with the given classname to stdClass and are generally used to enable the mocking of public static methods. Expectations set on the new mock object which refer to static methods will be used by all static calls to this class.
$mock = \Mockery::mock('overload:MyNamespace\MyClass');
Prefixing the valid name of a class (which is NOT currently loaded) with "overload:" will generate an alias mock (as with "alias:") except that created new instances of that class will import any expectations set on the origin mock ($mock). The origin mock is never verified since it's used an expectation store for new instances. For this purpose we use the term "instance mock" to differentiate it from the simpler "alias mock".
So to me it seems that overload does the same as alias with the difference that it also imports expectations from the origin mock.
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!