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.
Related
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.
Please pardon me for not knowing what the terminology of what I'm asking for is. I don't quite know what you'd call it so bear with me.
In Laravel 4 or 5, is there some way to set a default template for an Eloquent Model? For a long time, I've been writing lines like this: (just using book system as example)
$book = Sentry::getUser()->books()->find(14);
return View::make( "books.show" )->withBook($book);
Is there any way that I can make return $book; expand into return View::make( "books.show" )->withBook($book); ?
What you're asking makes sense. However, it doesn't really fit with Laravel's view of the world -- so keep in mind you're sort of striking out on your own.
When you
return View::make( "books.show" )->withBook($book);
from a route closure or controller action, Laravel treats anything returned as a view. This include regular strings -- i.e., you can do something like
return '<p>Foo</p>';
and Laravel will render the HTML fragment.
If you try this with a regular PHP object,
$o = SomeObject;
return $o;
you'll see an error something like this
The Response content must be a string or object implementing __toString(), "object" given.
This error happens because Laravel has tried treating the returned object as a string, and PHP has no default way of rendering an object as a string.
What PHP does have is a way for you, (the programmer), to tell it (PHP) how PHP should render an object if some code (Laravel) treats that object as a string. If you add a __toString method to any object, then PHP will use this method to render a string for the object. Try the following small program
<?php
class SomeObject
{
public function __toString()
{
return 'I am a rendered SomeObject';
}
}
$object = SomeObject;
// cast the object as a string ("treat" above)
echo (string) $object;
So -- this means you can, in any object, embed the logic for rendering a view. Assuming your book class is named Book, try the following
class Book
{
//...
public function __toString()
{
return View::make( "books.show" )->withBook($this);
}
}
and then in your route closure/controller action
$book = Sentry::getUser()->books()->find(14);
return $book;
One thing to keep in mind -- since there can only be one __toString definition, if you're extending classes that implement __toString, you may break someone else's functionality. To avoid that try using some sort of conditional rendering.
Hope that helps!
Update. Per the comments below. I can't speak to the Accepts header, although my instincts say Laravel doesn't do this as there's not a culture in PHP of looking at the Accepts header. I could be wrong. As for using it yourself in __toString, I'd nix that as well -- if a user is using your object during an Accepts JSON request, but needs to render it as a non JSON string for some reason, your code would interfere with that. Better to give you object rendering contexts, and then the people who render your object choose how it renders in the route closure, controller action, or a "IoCrewrite" of the rendering methods themselves.
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.
I'm having a problem with a line of code like:
$user->has('roles', ORM::factory('role', array('name' => 'unverified')))
I can mock the first argument, but can only assert that the 2nd argument returns a class. In some classes I make multiple uses of has and need to be able to test them properly. To be clear, I need to confirm that "unverified" was passed into the factory method of the 2nd argument. Any help is much appreciated.
The class I'm testing:
<?php
/**
* Policy class to determine if a user can upload a file.
*/
class Policy_File_Upload extends Policy
{
const NOT_LOGGED_IN = 1;
const NOT_VERIFIED = 2;
public function execute(Model_ACL_User $user, array $extra = NULL)
{
if ($user->can('login'))
{
return self::NOT_LOGGED_IN;
}
elseif ($user->has('roles', ORM::factory('role', array('name' => 'unverified'))))
{
return self::NOT_VERIFIED;
}
return TRUE;
}
}
and the corresponding test:
public function test_guest()
{
// User mock
$user = $this->getMock('Model_User', array('can', 'has'), array(), '', FALSE);
$user->expects($this->any())->method('can')->with('login')->will($this->returnValue(TRUE));
$user->expects($this->any())->method('has')->with('roles', $this->logicalOr
(
))
->will($this->returnCallback(array($this, 'roles')));
$policy = new Policy_File_Upload;
$this->assertSame(Policy_File_Upload::NOT_VERIFIED, $policy->execute($user));
}
You can always go the data-driven route of preparing test data for each case and verify the result of calling the function. In the end, you don't so much care that 'unverified' gets passed to ORM::factory() but rather that the correct result comes out of execute() based on the input.
Testing the implementation details makes the test brittle and difficult to write. If you can test the results instead, your tests won't break when you change how you arrive at the result. It may take longer to put together a data set for all your use cases, but it can generally be shared among tests.
To be clear, I need to confirm that "unverified" was passed into the factory method of the 2nd argument.
<?php
public function execute(Model_ACL_User $user, array $extra = NULL)
[...] ORM::factory('role', array('name' => 'unverified')) [...]
I'm sorry mate, you can't do that as there is no (sane) way to mock out a static method call.
The argument to why this is a problem are similar to one of Sebastian Bergmanns blog post: Testing-Code-That-Uses-Singletons
There are options like runkit that you could choose to replace the "ORM" class at runtime with a mocked version but thats really painfull.
So there are a couple of options:
You could create something like a Policy_File_Upload_Data_Provider that has a ->getRoles method where you put your ORM access. That would allow you to test your business logic nicely and testing that data access class should be a little easier as it doesn't to that much (just accessing the orm).
You could Inject your ORM class (or if that doesn't work out maybe just its name).
Depending on the class its self you should be able to create an instance and call ->factory like it would be a normal method. You could mock that.
If you don't want that just leaving that in there is also a not SO bad option.
Hope that at least gave you an idea/overview.
Additional Links why it's hard to test statics:
Misko Hevery - Flaw: Brittle Global State & Singletons
Kore Nordmann - Static considered harmful
You can actually do verification on parameters passed to mock methods utilizing parameter capturing in the Phake mocking framework for PHP.
public function test_guest()
{
// User mock
$user = Phake::mock('Model_User');
when($user)->can('login')->thenReturn(FALSE);
when($user)->has('roles', $this->anything())->thenGetReturnByLambda(array($this, 'roles'));
$policy = new Policy_File_Upload;
$this->assertSame(Policy_File_Upload::NOT_VERIFIED, $policy->execute($user));
Phake::verify($user)->has('roles', Phake::capture($factoriedRole));
$this->assertEquals('unverified', $factoriedRole->get('name'));
}
This example is assuming that you have the ability to check the name on $factoriedRole, obviously that piece needs to be changed depending on how that ORM stack works.
A small note, I agree with edorian regarding statics. If possible I would always prefer redesigning your code to not require statics, but sometimes that isn't possible, in which case this is a workable solution.
Phake Github Page
Phake Documentation on Parameter Capturing
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.