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:
The url is hardcoded, leaving you no way of altering it for development, testing or production
The Endpoint knows about how to retrieve data. From your code I cannot say what the endpoint really does, but if it's not a low level "Just get me Data" object, it should not know about how to retrieve the data.
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.
Related
I've been successfully using Mockery with PHPUnit tests lately. Yet, there is a dependency in a project I'm currently working that uses static method calls to interact with an API. I'm struggling to test one particular use case and it feels like I'll find other like this during the development roadmap.
Using this class as an example:
namespace Name\Space;
class User
{
/**
* #return \Name\Space\User[]
*/
public static function list(): array
{
// ...
}
public static function create(array $attrs): User
{
// ...
}
}
In case I just want to assert a method returns a primitive type, such as an array:
Mockery::mock('alias:\Name\Space\User')
->shouldReceive('list')
->andReturn([]);
It works fine, primarily because I'm not testing the array contents.
However, I have to call the create method, which returns an instance of the class itself (User). If I do something like this:
$user = new \Name\Space\User();
Mockery::mock('alias:\Name\Space\User')
->shouldReceive('create')
->andReturn($user);
The alias, obviously, won't work because the class was already loaded through the autoloader (composer's, in this case).
Does anyone have a suggestion on how to workaround this?
What about creating User in a closure?
<?php
$user = Mockery::mock('overload:\Name\Space\User')
->shouldReceive('create')
->andReturnUsing(function() {
return new \Name\Space\User();
});
Mocking static stuff is always painful.
I would recommend creating a Proxy object that is calling the static API calls and just returns the API results and inject this object everywhere you need to call the API.
This way it is easy to test by simply mocking the proxy object.
The proxy object itself can then be tested in an end to end test outside of the pure unit test scope.
You can still do more invasive stuff like this
https://www.pagemachine.de/blog/mocking-static-method-calls/?cn-reloaded=1
But writing code that doesn't belong to your unit tests purely to make something testable doesn't feel right to me.
We have Laravel 5 controllers method:
public function getInput()
{
$input = \Request::all();
$links = $input['links'];
$this->startLinks = explode("\n", $links);
return $this;
}
How can we test this single method? How to pass POST request with data to this method? And how to create instance of this controller class in my test method?
This looks like a method which probably shouldn't belong in a controller. If you are serious about testing, I'd highly recommend reading up on the repository pattern. When testing, it will make your life a lot easier to have everything abstracted out of the controllers.=
With that said though, this is still very testable. The main idea is to figure out what needs to be tested and ONLY test that. This means we don't care what the dependencies are doing, only that they are doing and returning something which the rest of the method will need. In this case, it's the Request facade.
Then you want to make sure the variables are set appropriately and that the method returned an instance of that class. It actually ends up being pretty straight forward.
Should look something like this...
public function testGetInput()
{
$requestParams = [
'links' => "somelink.com\nsomeotherlink.com\nandanotherlink.com\ndoesntmatter.com"
];
// Here we are saying the \Request facade should expect the all method to be called and that all method should
// return some pre-defined things which we will use in our asserts.
\Request::shouldReceive('all')->once()->andReturn($requestParams);
// Here we are just using Laravel's IoC container to instantiate your controller. Change YourController to whatever
// your controller is named
$class = App::make('YourController');
// Getting results of function so we can test that it has some properties which were supposed to have been set.
$return = $class->getInput();
// Again change this to the actual name of your controller.
$this->assertInstanceOf('YourController', $return);
// Now test all the things.
$this->assertTrue(isset($return->startLinks));
$this->assertTrue(is_array($return->startLinks));
$this->assertTrue(in_array('somelink.com', $return->startLInks));
$this->assertTrue(in_array('nsomeotherlink.com', $return->startLInks));
$this->assertTrue(in_array('nandanotherlink.com', $return->startLInks));
$this->assertTrue(in_array('ndoesntmatter.com', $return->startLInks));
}
I think you are looking for this.
If your test class extends TestCase you will get a lot of helper methods which will do heavy lifting for you.
function testSomething() {
// POST request to your controller#action
$response = $this->action('POST', 'YourController#yourAction', ['links' => 'link1 \n link2']);
// you can check if response was ok
$this->assertTrue($response->isOk(), "Custom message if something went wrong");
// or if view received variable
$this->assertViewHas('links', ['link1', 'link2']);
}
Codeception extends this functionality even further.
This is related to this question, but following that solution did not fix my issue. I also realize that Laravel's own documentation states that you should not mock the Request object, but I'm not sure how else to go about writing this test.
Here's a semblance of the code I want to test:
public function getThirdSegment()
{
return Request::segment(3);
}
Here's a test I currently have:
/**
* #test
*/
public function indexReturnsOk()
{
$this->prepareForTests();
$this->call('GET', '/api/v1/courses');
$this->assertResponseOk();
$this->assertResponseStatus(200);
}
This is failing because Request::segment(3) is returning null when running PHPUnit. I first tried to mock the Request object like this:
Request::shouldReceive('segment')->andReturn('courses');
But it still returns null. Then I tried this:
$request = m::mock('Illuminate\Http\Request');
$request->shouldReceive('segment')->andReturn('courses');
Input::swap($request);
And the segment method is still returning null. Is there any way to mock the return value of this method?
Update
This code is within a service provider's register method, but I don't think that's the cause of the issue. Hitting the site from a browser does what I would expect it to do, yet running PHPUnit doesn't seem to flesh out either the route or the URL, or anything having to do with the request itself.
Best answer here so far is I was doing it wrong. Service Providers run way before a controller is even loaded, and, when unit testing, Laravel's Illuminate\Foundation\Testing\TestCase loads the application (calling all service providers' both boot and register methods) during execution of the setUp method, way before any calls can be made out during the execution of any individual test.
I tried finding a solution by moving the logic down and got something to work, something along the lines of:
class MyTestClass extends TestCase
{
public function setUp()
{
// No call to parent::setUp()
// From: Illuminate\Foundation\Testing\TestCase
$this->app = $this->createApplication();
$this->client = $this->createClient();
// Not this one!
//$this->app->setRequestForConsoleEnvironment();
$this->app->boot();
// ...
}
public function testWhatever()
{
// Calls to this will now actually have a request object
$this->call('GET', '/api/v1/courses');
}
}
But that just can't be right, at least it doesn't feel so.
Instead, I figure it's probably best not to rely on anything in the Request object from within Service Providers. Instead, why not just inject an object that can do what I need it to do in the controller I want it to? No Service Provider necessary, and I can easily mock any object other than Request in Unit Tests. I should've believed the docs.
Update
Taking this answer to my own question a bit further, I believe my original mistake was that I was utilizing the Request object within a Service Provider. I think, on retrospection, that you should probably never use the Request object at all within a service provider, because providers get loaded for everything related to laravel (including artisan commands, which of course should have no request). My original code worked in the browser, but I probably would have noticed issues had I tried to run any artisan commands.
I came across this looking for a good way to handle a view composer that uses the request, and ultimately ended up going with constructor injection - because Laravel uses the Symfony Request class, it was really easy to "mock" a request with Request::create('http://myurl.com'), and pass it into an instance of my class for testing. Much better than trying to mess with stubbing methods on the request class, and doesn't rely on building up the whole Laravel application.
For clarity's sake, the view composer looks generally like this:
class SiteLayoutComposer extends BaseComposer {
function __construct(Request $request) {
$this->request = $request;
}
public function compose($view)
{
$templateName = preg_match('/siteurlexample/', $this->request->getHost()) ? 'site1' : 'site2';
$view->with('siteLayout', "layouts.sites.{$templateName}");
}
}
And the test:
class SiteLayoutComposerTest extends TestCase {
public function testWillSetTheSiteLayoutToSite1()
{
$request = Request::create('http://www.siteurlexample.com');
$view = View::make('home');
$composer = new SiteLayoutComposer($request);
$composer->compose($view);
$this->assertEquals('layouts.sites.site1', $view->siteLayout);
}
}
Currently I am working to test a class that generates XML based on a value object, sends the XML over HTTP and parses the XML response back into a second value object. I would like to test in this case the generated XML and the parsed value object based on a given XML.
The class looks like:
class MyClient
{
public function send(RequestValues $request)
{
$document = $this->generateMessage($request);
$response = $this->request($document);
return $this->parseResponse($response);
}
protected function generateMessage(RequestValues $request)
{
$document = new DomDocument;
// Do stuff with $request
return $document;
}
public function request(DomDocument $document)
{
$client = $this->getHttpClient();
$client->setRawBody($document->saveXml());
// Configure client
return $client->send();
}
public function parseResponse(Response $response)
{
$parameters = new ResponseValues;
$document = new DomDocument;
$document->loadXml($response->getBody());
// Fill in $parameters
return $parameters;
}
}
I would like to test two things:
Given a certain RequestValues argument, the generated XML must look like $string
Given a certain XML response value (the HTTP client will be mocked), the ResponseValues must be equal to $object
I am now writing a test for #1, but I think I can only achieve this via a callback. The callback however, does not give me quite useful information when the test fails. Only this message:
Failed asserting that DOMDocument Object () is accepted by specified callback.
The test looks like this:
public function testRequestContainsValidXml()
{
$client = $this->getMock('MyClient', array('request'));
$message = '';
$client->expects($this->once())
->method('request')
->with($this->callback(function($object) use ($message) {
return
($object instanceof DomDocument)
&& ($object->saveXml() === $message);
}));
$request = new DirectoryRequest;
$client->send($request);
}
The question is: how can I improve the test such that a normal string comparison is possible? I would love to get phpunit saying "string X is not equal to Y" which eases the debugging enormously.
PS. The complete code of this class is available on GitHub. Naturally, above example is a simplified version. Here's the actual class: https://github.com/juriansluiman/SlmIdealPayment/blob/master/src/SlmIdealPayment/Client/StandardClient.php#L58
PPS. If the code must be changed in order to get it tested, that's not a problem. I'd only want to keep the public API the same (i.e. the call ResponseValues send(RequestValues $request)
You class MyClient suggests that it is a facade. To test a facade in the context of UnitTests it is normally first of all necessary to test all units that are collaborators of that facade.
As you normally mock the collaborators - and not the unit under test - your test setup looks wrong as you're mocking the MyClient unit under test but not it's collaborators.
For example:
You want to test if the MyClient::parseResponse() method returns the expected ResponseValues object. Therefore you would mock the Response as it is a collaborator in this case.
You probably also want to mock the other collaborator (ResponseValues) however you can't as it is a hidden dependency that can not be injected (you could resolve this by injecting a factory to MyClient that can control the creation of such ResponseValues).
So much for testing MyClient::parseResponse(): You would inject the fixture XML via a Response mock and the run your assertions on the return value.
For the case to test if the request contains valid XML (testRequestContainsValidXml()), I don't think it should be done in that complicated form either. Sounds like you have a HTTP client here, you should only test that it works and don't care if it works with XML as well, because if it works, it works with XML as well. The reason why you test for valid XML is not because you want to test the client. So keep that test out of the client unit-tests.
The assertion for the string stuff btw. is:
$this->assertSame($expected, $actual);
Phpunit will show you a nice diff between the strings. Some other programmers / testers were also applying some XML normalization when they compared XML so that the diff is more readable. You might find a Q&A like PHP XML how to output nice format and related useful for that if your XML has no problem to deal with insignificant whitespace.
I hope I could pin-point the problem cases you face with this a little and broaden your view on the problem. I think you come most far with questioning your current mock usage critically, for the examples I give I have fully reduced mocking to providing collaborators, not to do assertions. Asswertions are only done on the unit under test, here the exemplary MyClient.
I'm trying to get my head round Unit Testing and there's one more piece of the jigsaw I need to find.
What I'm trying to do is write tests for the following code. In this case, I've got a really simple Front Controller (written in PHP).
class frontController
{
public function routeRequest($oRequest)
{
$sClassname = $oRequest->getController();
$sMethod = $oRequest->getAction();
$oController = new $sClassname();
$oResponse = $oController->{$sMethod}($oRequest);
return $oResponse;
}
}
The problem I have is because the code creates new objects. I can easily mock the request object so that I can tightly control what it will actually do within my test case. I'm not sure the best way to actually replace the controller with a test double.
This article from IBM suggests having a factory method for creating my controller and then overriding this with a specific class used for testing:
class frontController
{
public function routeRequest($oRequest)
{
$sMethod = $oRequest->getAction();
$oController = $this->createController($oRequest);
$oResponse = $oController->{$sMethod}($oRequest);
return $oResponse;
}
protected function createController($oRequest)
{
$sClassname = $oRequest->getController();
return new $sClassname();
}
}
and then for testing perhaps something like this:
class testFrontController extends frontController
{
public function setMockController($oMockController)
{
$this->oMc = $oMockController;
}
protected function createController($oRequest)
{
return $this->oMockController;
}
}
(note this isn't quite what the article says, but I'm thinking it would be most useful to me if it did this)
Another solution could be to have another class that creates the controller. This would then be a dependent class of the frontController. This way I can replace the factory/creation class during testing with a test double. Something like this:
class frontController
{
public function routeRequest($oRequest, $oControllerFactory)
{
$sMethod = $oRequest->getAction();
$oController = $oControllerFactory->create($oRequest);
$oResponse = $oController->{$sMethod}($oRequest);
return $oResponse;
}
}
class controllerFactory
{
public function create($oRequest)
{
$sClassname = $oRequest->getController();
return new $sClassname();
}
}
I guess the dependency injection could be taken care of in the front controller constructor or via a setter instead of a parameter to the actual "route" method.
I think I prefer option 2.
Is either of these two methods the right way of going about testing this kind of thing?
(perhaps "good way" would be better word here!)
Any thoughts or suggestions on option 1 vs option 2 appreciated or indeed any alternatives. Remember - the key thing is about how to test an object that itself creates other objects as part of its execution.
Thanks!
You might find this article handy.
It discusses how object creation should be separated from the actual running of the application.
I generally find factories to be a good thing to use for this scenario. In addition to the swappability aspect, it means that additional parameters, data, or dependencies required by the object being created can be stored by the factory, and so the object which actually requests the new object doesn't have to know anything about them...
You do not want to use the real controller but a mock, right ?
It seems to me the simplest way to achieve this would be to subclass the request so that it returns the name of a MockController.
I assume you have thought through your assertions so as to define the goal of what exactly you are testing. Keep in mind that unit tests are going to be testing the returns from your methods, which, in this case, is $oResponse (whatever this may be). As a result, your test assertions will be based on this return value. Since I don't know what that return value is from your code snippets, I can only demonstrate an example that you can complete.
I would recommend PHPUnit for your testing as it seems to be the most complete package for PHP imho (many are fans of SimpleTest, as well ... to each their own).
It would look something like this (Please note that I have left out includes for brevity. Read the PHPUnit documentation for more information):
class AimTest extends PHPUnit_Framework_TestCase{
private $_controller = null;
private $_request = null;
public function setUp(){
$this->_controller = new frontController();
//what does this object's type?
$this->_request = new requestObject();
}
public function testObjectCreation(){
/*
* note, that this is only one of several assertions that could
* be made depending on the return value
*/
$return = $this->_controller->routeRequest($this->_request);
//tailor to what you expect your output to be
$this->assertTrue($return == "my expected output");
}
Hope I didn't miss the mark completely on your stated purpose. Moral of the story is that you can only test what your methods return. If you want to test object instantiation from a method, use the instanceof PHP function against a method that returns that object after instantiation.