I am working on setting up a testing suite for a PHP Propel project using Phactory, and PHPUnit. I am currently trying to unit test a function that makes an external request, and I want to stub in a mock response for that request.
Here's a snippet of the class I am trying to test:
class Endpoint {
...
public function parseThirdPartyResponse() {
$response = $this->fetchUrl("www.example.com/api.xml");
// do stuff and return
...
}
public function fetchUrl($url) {
return file_get_contents($url);
}
...
And here's the test function I am trying to write.
// my factory, defined in a seperate file
Phactory::define('endpoint', array('identifier' => 'endpoint_$n');
// a test case in my endpoint_test file
public function testParseThirdPartyResponse() {
$phEndpoint = Phactory::create('endpoint', $options);
$endpoint = new EndpointQuery()::create()->findPK($phEndpoint->id);
$stub = $this->getMock('Endpoint');
$xml = "...<target>test_target</target>..."; // sample response from third party api
$stub->expects($this->any())
->method('fetchUrl')
->will($this->returnValue($xml));
$result = $endpoint->parseThirdPartyResponse();
$this->assertEquals('test_target', $result);
}
I can see now, after I tried my test code, that I am creating a mock object
with getMock
, and then never using it. So the function fetchUrl
actually executes, which I do not want. But I still want to be able to use
the Phactory created endpoint
object, since it has all the right fields
populated from my factory definition.
Is there a way for me to stub a method on an existing object? So I could stub
fetch_url
on the $endpoint
Endpoint object I just created?
Or am I going about this all wrong; is there a better way for me to unit test my functions that rely on external web requests?
I did read the PHPUnit documentation regarding "Stubbing and Mocking Web Services", but their sample code for doing so is 40 lines long, not including having to define your own wsdl. I'm hard pressed to believe that's the most convenient way for me to handle this, unless the good people of SO feel strongly otherwise.
Greatly appreciate any help, I've been hung up on this all day. Thanks!!
From a testing perspective, your code has two problems:
With your code like this, there is no good way to test your code. You could work with Reflections, changing your code and so on. The problem with this approach is that you don't test your actual object but some reflection which got change to work with the test.
If you want to write "good" tests, your endpoint should look something like this:
class Endpoint {
private $dataParser;
private $endpointUrl;
public function __construct($dataParser, $endpointUrl) {
$this->dataPartser = $dataParser;
$this->endpointUrl = $endpointUrl;
}
public function parseThirdPartyResponse() {
$response = $this->dataPartser->fetchUrl($this->endpointUrl);
// ...
}
}
Now you could inject a Mock of the DataParser which returns some default response depending on what you want to test.
The next question might be: How do I test the DataParser? Mostly, you don't. If it is just a wrapper around php standard functions, you don't need to. Your DataParser should really be very low level, looking like this:
class DataParser {
public function fetchUrl($url) {
return file_get_contents($url);
}
}
If you need or want to test it, you could create a Webservice which lives within your tests and acts as a "Mock", always returning preconfigured data. You could then call this mock url instead of the real one and evaluate the return.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With