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.
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'm working on a multi-tenant app where I need to log a lot more data than what I pass to the log facade. What I mean is, every time I do this...
Log::info('something happened');
I get this:
[2017-02-15 18:12:55] local.INFO: something happened
But I want to get this:
[2017-02-15 18:12:55] [my ec2 instance id] [client_id] local.INFO: something happened
As you can see I'm logging the EC2 instance ID and my app's client ID. I'm of course simplifying this as I need to log a lot more stuff in there. When I consume and aggregate these logs, having these extra fields make them incredibly handy to figure what things went wrong and where.
In the Zend Framework, I usually subclass the logger and add these extra fields in my subclass but I'm not sure how I can do that with Laravel. I can't find where the logger is instantiated so that I can plug my custom logger in (if that is even the way to go in Laravel).
So, I'm not asking how to get the EC2 instance ID and the other stuff, I'm only asking what the proper way to "hot wire" the Laravel logger is to be able to plug this in.
Just an idea... the logger in Laravel is really a Monolog instance... You could push a handler on it and do whatever processing you want for each entry... like so...
<?php
$logger->pushProcessor(function ($record) {
$record['extra']['dummy'] = 'Hello world!';
return $record;
});
As per the Laravel doc you can hook up into the monolog config at boot...
Custom Monolog Configuration
If you would like to have complete control over how Monolog is
configured for your application, you may use the application's
configureMonologUsing method. You should place a call to this method
in your bootstrap/app.php file right before the $app variable is
returned by the file:
$app->configureMonologUsing(function ($monolog) {
$monolog->pushHandler(...);
});
return $app;
So instead just push a processor on the $monolog instance passed to the hook...
Just an idea, I have not tried this in Laravel but used Monolog before...
In my controller after response I have to do some work. What is better to use:
1. Listen to kernel.terminate event
or
2. Dispatch my custom event
?
Why kernel.terminate?
As you can see, by calling $kernel->terminate after sending the
response, you will trigger the kernel.terminate event where you can
perform certain actions that you may have delayed in order to return
the response as quickly as possible to the client (e.g. sending
emails).
But on the other hand is it ok to check every request in my subscriber?
kernel.terminate happens after the response is sent, and can be useful for some "heavy" operations you can perform after the client has received the response. There are a few downsides however, mainly that if something goes wrong, there is no way to give the appropriate feedback to the user (for example to try again or to report a problem). Additionally, not all errors may be logged (see https://github.com/symfony/symfony/issues/19078).
Since you want to publish jobs to a Gearman queue, I would suggest avoiding using kernel.terminate, since typically publishing a job does not involve significant resources, and should be possible to do before sending the response. So you could trigger your custom event, or perhaps even avoid the event dispatcher completely by doing a more explicit call in your controller.
You won't be able to have your own event doing work after the response without using kernel.terminate. Because this is the only action that may occur after the response. We can confirm this by having a look at the front controller app.php:
$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);
As a note, kernel.terminate will work only if you use PHP-FPM. Otherwise, no solution outside of using some message queue.
Finally, a common pattern is to dynamically add a listener on kernel.terminate. From inside your controller, assuming you need to call my_service:
$myService = $this->get('my_service');
$this->get('event_dispatcher')->addListener('kernel.terminate', function (Event $event) use (myService) {
$myService->doSomething();
});
I have several integration tests with phpunit,
and in the proccess of the tests there are some logs written to files in the system.
I would like to check if a line was written during a test, is that possible?
example:
/** #test */
function action_that_writes_to_log() {
$this->call('GET', 'path/to/action', [], [], $requestXml);
//I want this:
$this->assertFileHas('the log line written', '/log/file/path.log');
}
The obvious way:
Implementing a custom assertion method, like the one you propose: assertFileHas. It's quite easy, just check if the string appears in the file. The problem you can get is that the line can already exist from another test or the same test already run. A possible solution for this is deleting the logs content before each test or test class, depending on your needs. You would need a method that deletes the logs and call it from setUp or setUpBeforeClass.
I would go with another approach: mocking the logging component, and checking that the right call is being done:
$logger_mock->expects($this->once())
->method('log')
->with($this->equalTo('the log line written'));
This makes easy to test that the components are logging the right messages, but you also need to implement a test that verifies that the logger is capable of actually writting to the file. But it's easier to implement that test once, and then just check that each component calls the logging method.
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.