i have a problem with writing tests for my action that gets called by jquery via ajax. i don't know how to catch the data that is being sent back to the view, by the action, so that i could assert if it's correct. my $.ajax(...) script inserts result, echo-ed by the controller, into a textarea element, but in my test script, result is null. failure message:
Failed asserting that null matches expected '...
here's what my test code so far:
$this->getRequest()->setRawBody('some json containing input params im testing');
$this->getRequest()->setMethod('GET');
$this->getRequest->setHeader('HTTP_X_REQUESTED_WITH','XMLHttpRequest');
$this->dispatch('my url');
$result = json_decode($this->getResponse()->getBody(),true);
$expectedResult = 'some string';
$this->assertEquals($expectedResult, $result['targeted element']);
The recommendation I write below is how I test and has proven to be much more useful and less error prone:
No "controller" testing.
Inject models and value objects into your services.
Inject services into your controllers.
Test models, value objects, and services.
Use a JavaScript testing framework to test your JS -- There are tons of good options out there that will allow you to mock your Ajax calls.
Related
I am working on an extension (app) of nextcloud (which is based on Symfony). I have a helper class to extract data from the request that is passed by the HTTP server to PHP. A much-reduced one could be something like this (to get the point here):
<?php
namespace OCA\Cookbook\Helpers;
class RequestHelper {
public function getJson(){
if($_SERVER['Request_Method' === 'PUT'){ // Notice the typos, should be REQUEST_METHOD
$raw = file_get_content('php://input');
return json_decode($raw, true);
} else { /* ... */ }
}
}
Now I want to test this code. Of course, I can do some unit testing and mock the $_SERVER variable. Potentially I would have to extarct the file_get_content into its own method and do a partial mock of that class. I get that. The question is: How much is this test worth?
If I just mimick the behavior of that class (white box testing) in my test cases I might even copy and paste the typo I intentionally included here. As this code is an MWE, real code might get more complex and should be compatible with different HTTP servers (like apache, nginx, lighttpd etc).
So, ideally, I would like to do some automated testing in my CI process that uses a real HTTP server with different versions/programs to see if the integration is working correctly. Welcome to integration testing.
I could now run the nextcloud server with my extension included in a test environment and test some real API endpoints. This is more like functional testing as everything is tested (server, NC core, my code and the DB):
phpunit <---> HTTP server <---> nextcloud core <---> extension code <---> DB
^
|
+--> RequestHelper
Apart from speed, I have to carefully take into account to test all possible paths through the class RequestHelper (device under test, DUT). This seems a bit brittle to me in the long run.
All I could think of is adding a simple endpoint only for testing the functionality of the DUT, something like a pure echo endpoint or so. For the production use, I do not feel comfortable having something like this laying around.
I am therefore looking for an integration test with a partial mock of the app (mocking the business logic + DB) to test the route between the HTTP server and my DUT. In other words, I want to test the integration of the HTTP server, nextcloud core, my controller, and the DUT above without any business logic of my app.
How can I realize such test cases?
Edit 1
As I found from the comments the problem statement was not so obviously clear, I try to explain a bit at the cost of the simplicity of the use-case.
There is the nextcloud core that can be seen as a framework from the perspective of the app. So, there can be controller classes that can be used as targets for URL/API endpoints. So for example /apps/cookbook/recipe/15 with a GET method will fetch the recipe with id 15. Similarly, with PUT there can be a JSON uploaded to update that recipe.
So, inside the corresponding controller the structure is like
class RecipeController extends Controller {
/* Here the PUT /apps/cookbook/recipe/{id} endpoint will be routed */
public function update($id){
$json = $this->requestHelper->getJson(); // Call to helper
// Here comes the business logic
// aka calls to other classes that will save and update the state
// and perform the DB operation
$this->service->doSomething($json);
// Return an answer if the operation terminated successfully
return JsonResponse(['state'=>'ok'], 200);
}
}
I want to test the getJson() method against different servers. Here I want to mock at least the $this->service->doSomething($json) to be a no-op. Ideally, I would like to spy into the resulting $json variable to test that exactly.
No doubt, in my test class it would be something like
class TestResponseHandler extends TestCase {
public function setUp() { /* Set up the http deamon as system service */}
public testGetJson() {
// Creat Guzzle client
$client = new Client([
'base_uri' => 'http://localhost:8080/apps/cookbook',
]);
// Run the API call
$headers = ...;
$body = ...;
$response = $client->put('recipe/15', 'PUT', $headers, $body);
// Check the response body
// ....
}
}
Now, I have two code interpreters running: Once, there is the one (A) that runs phpunit (and makes the HTTP request). Second, there is the one (B) associated with the HTTP server listening on localhost:8080.
As the code above with the call to getJson() is running inside a PHP interpreter (B) outside the phpunit instance I cannot mock directly as far as I understand. I would have to change the main app's code if I am not mistaken.
Of course, I could provide (more or less) useful data in the test function and let the service->doSomething() method do its job but then I am no longer testing only a subset of functions but I am doing functional or system testing. Also, this makes it harder to generate well-aimed test cases if all these side-effects need to be taken into account.
I am currently busy with a PSR-7 project with responses and requests.
Currently we are setting up an application in our index.php by doing something like:
$app = new Application();
$app->loadConfiguration(
'../config/global.yml',
);
// Should return the response?
$app->run((new ServerRequestFactory())->createServerRequestFromGlobals());
Here the run method also calls an emit method that is responsible for sending the headers and printing the body of the response.
The request and respons are now linked together in one call which makes it hard to test since you don't want to send the response with the headers straight to PHPUnit.
I have removed the emit call in the chain of the run method and added this to the index after the run method call:
// Send the response.
$app->send();
This way they are decoupled but the downside is I now have to hold a instance of my response in a response property inside my Application.php($app) class.
I want to move the response instance to the response class itself but my co-workers thinks a class should never hold an instance of itself. Yet when I look at frameworks this happens quite a lot. Is he right about this?
What arguments can I make to decouple my request and response besides easier testing?
I am pretty new to unit testing, one of the arguments I have already heard is that I should not test the full application anyways but rather separate components and therefore should not be worried about de-coupling the request and response.
I'm testing an object that is designed to test if user owns a given email. So on invocation of "tryEmail" method, it sends a message with confirmation link, to the given email address. My test looks like this:
public function testSendingWasSuccessful() {
$confirmationObject = $this->getMock('LT\EmailConfirmation\Model\ConfirmationObjectInterface');
$testType = 'test.type';
$testEmail = 'test#example.com';
$testData = [];
// EmailTester should create a new confirmation object.
$this->manager->expects(static::once())
->method('create')->with($testType, $testEmail)
->willReturn($confirmationObject);
// Then it should send the confirmation message.
$this->mailer->expects(static::once())
->method('send')->with(static::identicalTo($confirmationObject))
->willReturn(true);
// And save the confirmation object.
$this->manager->expects(static::once())
->method('save')->with(static::identicalTo($confirmationObject));
$tester = new EmailTester($this->repository, $this->manager, $this->confirmationHandler, $this->mailer);
static::assertTrue($tester->tryEmail($testType, $testEmail, $testData));
}
Now you can see what is possibly wrong with it - it contains multiple assertions. Why I decided to use those assertions inside of the one test? Because they are dependent on each other. So, the confirmation message should be sent only if new confirmation object has been created, and confirmation object should be saved only if confirmation message was send, and at the end, the output of the "tryEmail" method, using those mocked methods is being asserted.
However, I feel like I accidentally just described the implementation of the "tryEmail" method with my assertions. But it seems to be required for full coverage of this method, and making me sure that it always work as it should. I can imagine bugs passing by if I would remove any of those assertions. For example: static::identicalTo($confirmationObject) which is basically: check if the object passed to the mailer is the same as the one created before. If I would change interface of the mailer, I would have to change also this test of the EmailTester, so it seems like I'm doing something wrong here. At the same time however - how can I check for the above assertion without introducing this coupling? Or maybe should I just leave this untested?
Am I doing it right or wrong? How could I improve on it? When to use assertions on mocks really?
Added: I just had a thought - is it not true that testing class is supposed to test the implementation (if the implementation conforms with the interface)? That would mean that describing implementation in the test is actually a good thing, because it makes sure that implementation works correctly. That would also mean that level of coupling of the implementation will be carried over to the test, and is unavoidable. Am I wrong here?
The rule of "one assertion per test" is to keep your tests focused on one particular behavior of the code being tested. Having multiple assertions in a test is not a bad thing.
When using a mock object, I prefer to have some sort of assertions on methods being replaced. That way I ensure that the system will be using the dependencies as expected.
You testing class is to confirm behaviors of your code. The assertions that you have would be any of the checks that you would do manually to ensure that the class was behaving as you expected. Since you are expecting particular methods to be called in a specific manner, you want to have an assertion for them.
Issues that I see with the test are that you have a mock object returning a mock object. This is usually a code smell that means you are not passing the correct dependencies. You could possibly move the creation of your LT\EmailConfirmation\Model\ConfirmationObjectInterface object out of the method and pass that as a dependency of your method. Replacing the first two parameters of your method with this object.
You also don't seem to be using the third parameter at all in this test so it doesn't appear to be necessary.
I am testing a controller action using a functional test in Symfony. In this test I am doing something like this:
$client->request(
'PUT',
'/api/nodes/',
$data
);
Afterwards I would like to test if a certain event has been dispatched. I already tried to enable the profiler previously (and set the config accordingly) and check the data in the EventDataCollector:
$client->enableProfiler();
$client->request(
'PUT',
'/api/nodes/' . $data[0]['id'] . '?webspace=sulu_io&language=en',
$data[0]
);
/** #var EventDataCollector $eventDataCollector */
$eventDataCollector = $client->getProfile()->getCollector('events');
This works as expected, but the problem is that the $eventDataCollector only contains data about the events for which some listeners have actually been executed. Fortunately there is an event listener executed in this specific case, but I would like that to work also without any event listeners attached, since I can't say for sure that this situation will continue to be like that.
So my question is if there is a way to test if a event is dispatched, which is save, even if there wasn't a event listener attached.
You could register an event listener/subscriber in your test environment only. Its sole purpose would be to enable you to inspect if the event was fired.
Yagni. Functional tests should be based on the specifications, e.g. sending some data to PUT /api/nodes/ HTTP/1.1 should result with something (ideally) valuable for API consumers. Some data manipulations, I suppose. The test should confirm the output matches expectations for specific data permutations.
Event listening is an internal implementation of your black box and is not subject of functional testing. It should be tested in isolation. Enabling profiler, you basically change the system under test, and end up testing something that only partially related to the production code.
Should I extend the ControllerTestCase and create a custom method to handle this? What is the best way?
AJAX response should be no different than any other response. So no subclassing should be necessary.
But if you mean "how to test that the response AJAX request generated from the file I'm testing is correct?" there is no way to do that (except Selenium tests maybe). And it's not PHP's job to test this - you should use JavaScript tests to test it.
When you're sure the AJAX is calling correct URL with correct params, you can create the AJAX request artificaly and test the response as normal request.