I am new to unit testing and now trying to write some test cases using PHPUnit for a Laravel application. I have done one basic test which is like the following :
public function testGetPointsByApp()
{
$this
->json(
'GET',
self::URL.'/'.$value
)
->assertResponseStatus(200)
->seeJson([
'status' => 'success',
])
->seeJsonStructure([
'status',
'message',
'data' => []
]);
}
Now this is working. But I would like to know how can I test for invalid inputs and for the cases like there is not data at all with the given parameters. I am throwing corresponding exceptions in each of these cases and it will add particular messages. How can I test whether it is thrown ?
I don't want to include it to this method and would like to write some other methods to test different scenarios in the API like invalid UUID, no data etc. So for each time, I need to call the API url or this call can be done from any kind of set up function ?
I don't know laravel but in PHPUNIT you can test if exception is thrown with expectException function. For example for InvalidArgumentException
$this->expectException(InvalidArgumentException::class);
You can also use annotation #expectedException and there are also a lot of things that can be tested in exception like expectExceptionCode() etc.
More you can find here
Since you're not doing an Unit test (this kind of test you're doing is an acceptance test), you cannot check for the exception it self, you will look for a bad response:
$this
->json(
'GET',
self::URL.'/'.$BAD_VALUE
)
->assertResponseStatus(422)
->seeJson([
/* Whatever you want to receive at invalid request */
])
->seeJsonStructure([
/* Whatever you want to receive at invalid request */
]);
Laravel validation errors returns 422 - unprocessable entity HTTP code.
You can check the response also for your expected error messages or something else.
Related
When i try to validate controller response using one of the available assertions like assertJsonValidationErrors it gives the following error:
Failed to find a validation error in the response for key: 'name'
Here is my test:
Mesh::factory()->create(['name'=>'test']);
$this->post(route('meshes.store', ['name' => 'test']));
$this->response->assertUnprocessable()->assertJsonValidationErrors('name');
Validation response format in Lumen is different than Laravel which wraps the errors in errors property.
Simply pass null to the second parameter of assertJsonValidationErrors or any other methods available for validation assertion which has this parameter and you should be fine:
$this->response->assertUnprocessable()->assertJsonValidationErrors('name', null);
I'm working on a legacy code and trying to use tests to cover the code for later refactoring. In one controller there's a piece of code like this:
return redirect()->route('login')->withErrors(['invalid credentials']);
Here are my assertions for the test:
$response->assertRedirect(route('login'));
$response->assertSessionHasErrors([
0 => 'invalid credentials'
]);
But the problem is I get the following output:
Session missing error: 'invalid credentials'
Failed asserting that false is true.
Any idea how can I get this done?
Looking at the source code of assertSessionHasErrors() I think it cannot be done with your use case. The problem is the unnamed array in your withErrors(...) method. I see two options for you:
Change your legacy application into:
return redirect()->route('login')->withErrors(['error' => 'invalid credentials']);
Afterwards you can run your test like so:
$response->assertRedirect(route('login'));
$response->assertSessionHasErrors([
'error' => 'invalid credentials'
]);
If you cannot change your legacy application you could use this:
$response->assertRedirect(route('login'));
$this->assertEquals(session('errors')->getBag('default')->first(),'invalid credentials');
I am working on an Symfony + FOSRestBundle based REST API. I have implemented (extended) custom HttpException class which is being used in case of validation errors. Along the error message I always pass an array with detailed data about validation problems which is generated by validator class. That is basically the reason why I extended base HttpException. My idea is to show array of errors from exception to the end user of API, but with the core functionality of Symfony I always get "400 Bad request" error in JSON format (no possibility to show another key/value pair along status code and message). I started looking how could I overcome this problem and tried various principles (normalizers, listeners, ...) which I found after searching the web, but none of those really solved the problem of showing the array of errors along code and message.
I'm using Twig for generating responses (I love detailed HTML errors in dev mode, so there's no way for me to replace it with something else). I checked how HTTP kernel processes the request when an exception is thrown and found out that everything goes via exception listener to the exception controller. I created custom exception controller and tried to get the array of errors from the exception. However, the real exception is wrapped into FlattenException which discards that array. The main problem here is actually FlattenException object, because I can't get the data from it about the errors (I know why flatten exception is used there, but in my case is not really useful since I have the real array, not some active object instead of it).
I would like to somehow replace FlattenException with my custom class - I want extend it just to have another property for array of messages that would be preserved until exception controller's showAction and rendering of view template. Then I would change static create function with additional logic for handling cases when my custom type of exceptions is being wrapped. I'm not sure if that's even possible without changing the core code of Symfony and if possible I'd like to stay away from any hacks.
Another thing about which I was thinking is to extend/replace ExceptionListener. There I would just change existing logic to replace FlattenException with MyCustomFlattenException which would then get passed to my ExceptionController via showAction method. I already tried that and overrode both twig.exception_listener and http_exception_listener services (separately - once the first and in the other case the second; I did that in services.yaml), but didn't have much luck with it.
For the twig.exception_listener this is what is in my service ...
twig.exception_listener:
class: App\EventListener\ApiExceptionListener
arguments:
$controller: "%twig.exception_listener.controller%"
$logger: "logger"
tags:
- name: kernel.event_subscriber
- name: monolog.logger
channel: request
... and I always get the this error:
Fatal error: Uncaught Symfony\Component\DependencyInjection\Exception\RuntimeException: Cannot autowire service "App\EventListener\ApiExceptionListener": argument "$controller" of method "Symfony\Component\HttpKernel\EventListener\ExceptionListener::__construct()" has no type-hint, you should configure its value explicitly. in /project/vendor/symfony/dependency-injection/Compiler/DefinitionErrorExceptionPass.php on line 54
While for http_exception_listener this is what's in my service ...
http_exception_listener:
class: App\EventListener\ApiExceptionListener
arguments:
[
null,
null,
"%kernel.debug%",
"%kernel.charset%",
"%debug.file_link_format%",
]
tags:
- name: kernel.event_listener
event: kernel.exception
method: onKernelException
priority: -1024
- name: kernel.reset
method: reset
... and there is no any error, but still the core ExceptionListener is called instead of the one I specified in services.
This is the ApiExceptionListener class where I have some breakpoints in my IDE to check if it was really called:
<?php
namespace App\EventListener;
use Symfony\Component\HttpKernel\EventListener\ExceptionListener;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
class ApiExceptionListener extends ExceptionListener
{
public function onKernelException(GetResponseForExceptionEvent $event)
{
$test = 'ok';
}
protected function duplicateRequest(\Exception $exception, Request $request)
{
parent::duplicateRequest($exception, $request);
}
}
I would really appreciate any help or idea to get in the right direction of solution for this problem. Thank you!
I use laravel 5.4. When in my tests I do something like:
$this->get("/api/test")->assertStatus(422)->assertJson([
"status" => "ok"
]);
I get an HttpException when status code is 422, even though I'm totally ready for this -- and I'm even expecting it. My problem is that exception prevents my asserts, so that assertJson just doesn't work.
$this->expectException(HttpException::class) doesn't help since it catches this exception and stops, so this begins to pass:
$this->expectException(HttpException::class)
$this->get("/api/test")->assertStatus(422)->assertJson([
"status" => "ok"
]);
$this->assertTrue(false);
I see I can catch this exception myself, but then I won't be able to apply my asserts to the response in an easy way. How can I work around that trouble?
The validation errors only get converted to json response on a json or ajax request. Try this.
$this->getJson("/api/test")->assertStatus(422)->assertJson([
"status" => "ok"
]);
I've just started with php unit.
In my test cases UsersController should return:
public function UsersController() {
....
throw new \Cake\Network\Exception\NotFoundException();
}
phpunit code
$this->setExpectedException('Cake\Network\Exception\NotFoundException');
returns an assertion failed that looks like:
App\Test\TestCase\Controller\UsersControllerTest::testActivate
Failed asserting that exception of type "Cake\Network\Exception\NotFoundException" is thrown.
Meanwhile browser return a 404 PageNotFound and $this->assertResponseOk() returns:
App\Test\TestCase\Controller\UsersControllerTest::testActivate
exception 'Cake\Network\Exception\NotFoundException' with message 'Not Found' in /vagrant/ifmx.local/src/Controller/UsersController.php:215
Does somebody know why it's happened? And is there any way to get exception message in unit test.
You seem to have misunderstood how the integration test case works.
It doesn't just wrap a call to a controller method, like $controller->action(), it simulates a whole request, and as such, exceptions are being catched, and result in error pages being rendered, just like it happens when you are visiting a URL in your browser.
So PHPUnit will never know about the exception, and thus you cannot use the expected exception feature as you would when directly testing specific code parts where exceptions bubble up to PHPUnit. If that would happen, then it wouldn't be possible to assert responses, which one might want to do, even when an exception has been thrown.
Possible exceptions are being buffered in the IntegrationTestCase::$_exception property, which you can for example use to test if, what kind of exception has been thrown, etc, like
$this->assertInstanceOf('\Cake\Network\Exception\NotFoundException', $this->_exception);
$this->assertEquals('Not Found', $this->_exception->getMessage());