Laravel testing assertions against session and response - php

When testing a route in Laravel I cannot seem to assert against both the returned response and the session. Is there any reason for this and should I instead be splitting the test into two?
Here's my test, simplified:
$response = $this->call('POST', route('some-route'), $data);
This passes:
$this->assertResponseStatus(302);
This doesn't pass:
$this
->assertResponseStatus(302)
->assertSessionHasErrors([
'application_status' => 'Application status has changed. Action not applied.'
]);
The test will throw up an error saying it can't assert against null.
I've tried moving the order of the tests round and also assigning the response and session to variables before asserting like so:
$response = $this->call('POST', route('admin.application.action.store'), $data);
$sessionErrors = session()->get('errors');
$this
->assertEquals(302, $response->status())
->assertTrue($sessionErrors->has('application_status'));
But still get the same issue. Error: Call to a member function assertTrue() on null
Any advice would be greatly appreciated. Thanks!

Assertions don't implement fluid interface. Run it as 2 sequential statements:
$this->assertResponseStatus(302);
$this->assertSessionHasErrors([
'application_status' => 'Application status has changed. Action not applied.'
]);

Related

Laravel Exception handler logs also the request

I'm working on a Laravel 5.8 project, and I want to log inside the daily log file the request that has caused the exception, if some exception occurs.
I've tried this in the public function report(Exception $exception)
parent::render(request());
but it doesn't work at all. I've also tried this
\Log::error(json_encode(request()));
But it logs this
local.ERROR: {"attributes":{},"request":{},"query":{},"server":{},"files":{},"cookies":{},"headers":{}}
How should i do it? I need it in order to understand which request has caused that exception, and if it's possible, i need to log also other values, but i think that solved this, i can reuse the code to logs also the others
You can't just json_encode() the entire request as many properties are private/protected and require the use of getters for access. You will need to determine which values are important to you and build an appropriate response.
$response = [
'method' => request()->method(),
'url' => request()->url(),
'full_url' => request()->fullUrl(),
'data' => request()->all(),
];
Then you can pass your response array as a second parameter to the log handler, without needing to use json_encode().
\Log::error('Request details: ', $response);

How can multiple method calls be chained on slim's response object?

I want to chain multiple method calls on slims's $response object, but if i do i get an error ( status 500) and nothing happens.
This might aswell be a lack of basic PHP knowledge, i am not very experienced in PHP and it is my first time working with slim, or any serverside / API framework for that matter.
I have tried flipping arround the order of the calls, and doing them in different lines but to no awail. The long term goel of this, to build an API for an update application. So i will have to handle get requests with multiple parameters, evaluate them and depending on the results, do and return deffirent responses.
// this one fails, i set the status to 900 on purpose just to see what happens
$app->get('/', function (Request $request, Response $response, array $args) {
$response->getBody()->write("Slim main page")->withStatus(900);
return $response;
});
The first example does give me an 500 error on the network tab. This would suggest some type of syntax error i guess? If i alter this route a little bit to look like this :
# this one works fine, except the status code setting gets ignored, but why?
$app->get('/', function (Request $request, Response $response, array $args) {
$response->write("Slim main page")->withStatus(900);
return $response;
});
things alsmost work out, but the status code is not set for some reason.
I would expect the first one, to return the string "slim main page" with the status code 900. Even if i use a non made up status code, this setting gets ignored.
The second code block ist just an alteration for testing purposes.
I am pretty sure this a newby thing but i am really lost here, so any advice, or some fool proof articles / docs besides the slim docs are appreciated.
The write method returns the number of bytes written to the stream (and not the new response object). Try this:
$app->get('/', function (Request $request, Response $response, array $args = []) {
$response->getBody()->write('Slim main page');
$response = $response->withStatus(200);
return $response;
});
Notice 1: Enable the error details on dev: 'displayErrorDetails' => true
Notice 2: The HTTP code 900 is an invalid status code for Slim and will throw the following execption.
Type: InvalidArgumentException
Message: Invalid HTTP status code
File: vendor/slim/slim/Slim/Http/Response.php
Line: 228

Assert validation errors on Laravel 5.1

Hi I'm trying to create a test for a laravel controller not dependent on the view. I have the following method in my controller class:
public function store(Request $request)
{
$this->validate($request,[
'name'=>'required',
'login'=>'required',
'password'=>'required'
]);
//TODO: Store to database
return redirect('usuario/nuevo');
}
And I found the following code to test whether the request had any errors:
public function testStore()
{
$response=$this->call('GET','usuario.store');
$this->assertSessionHasErrors();
}
As it is this test should pass since I'm sending a request without the required field filled out however PhpUnit returns the following message:
Session missing key: errors
Failed asserting that false is true.
The only way I can make it work is to try to "see" the error message on the response page by making the test like:
public function testStore()
{
$this->visit('/usuario/nuevo')
->press('Crear')
->see('Whoops');
}
This doesn't work for me for two reasons: 1)The test depends on the view to "press" the button and send the request(I wish to test this in a different test and keep this one strictly for the controller) and 2)The test depends on the 'Whoops' string to be present which is obviously bad practice.
I have found several answers claiming that the fist test code should work, but it simply doesn't.
Try it with session start and send empty parameters
$this->startSession();
$this->call('GET','usuario.store', array(
'name' => '',
'login' => '',
'password' => ''
));
$errors = session('errors');
$this->assertSessionHasErrors();
$this->assertEquals($errors->get('name')[0],""your custom error message);// only for checking exact message
Your problem is you are not testing what you think you are testing.
To execute the controller's store() you should use a POST method and not GET unless your routes are really weird.

Cannot test redirects when unit testing Laravel 5

Here is my test code:
public function testRegistrationFailsIfNameIsEmpty()
{
$this->flushSession();
$response = $this->call('POST', '/signup', ['fullname' => '']);
$this->assertSessionHasErrors('fullname'); // Passes, expected
$this->assertTrue($response->isRedirection()); // Passes, expected
$this->assertRedirectedTo('/signup'); // Fails, unexpected.
}
When I call that method, it's validating the input, and if the validation fails, it redirects me back to /signup to show the validation errors. I've manually tested this in the browser, and it works as expected.
However, when I run the above unit test, the last assertion fails, and it thinks I've been redirected to just / rather than /signup.
I have no idea why it's doing this. If I test that a redirect happened at all, the test passes because a redirect does happen, it just thinks the redirect is to / instead of /signup.
I've disabled all middleware so I know it's not something like guest middleware thinking I'm logged in when I'm not.
EDIT: Test Results:
There was 1 failure:
1) RegistrationTest::testRegistrationFailsIfNameIsEmpty
Failed asserting that two strings are equal.
--- Expected
+++ Actual
## ##
-'http://localhost/signup'
+'http://localhost'
Using request()->validate() will return you to the calling url if validation fails. When running unit tests, there is no calling page so you will be directed to home.
https://laravel.com/docs/5.5/validation#quick-writing-the-validation-logic
if validation fails, an exception will be thrown and the proper error
response will automatically be sent back to the user. In the case of a
traditional HTTP request, a redirect response will be generated
So you either need to test that your redirect is to home, or fudge the calling url. Though then you're just testing the validator rather than your code, which doesn't really add anything:
session()->setPreviousUrl('/calling/url');
https://www.neontsunami.com/posts/testing-the-redirect-url-in-laravel
Edit:
Actually, this can be useful when the test expects the redirect to go to a route with a named parameter.
Edit for Laravel 5.8:
The test should be called with:
$this->from('/calling/url')
->get('/some/route')
->assertRedirect('/somewhere/else');
I had this same issue when testing a Laravel app. It seems that when you use $this->call in the test and then your controller uses something like Redirect::back(), the redirect is sent to '/'. I believe this is because you weren't on any page when you made "$this->call" so the application is unsure where to redirect you, unless you are explicit about the redirect in the controller, i.e. redirect('somePage').
However when doing something like $this->actingAs($user)->visit('somePage')->press('someButton'), you are correctly sent back to the expected page when using Redirect::back(). Likely because the app knows what page you started on.
You have to set ->from(route('RouteName')) in order to make sure the redirection assertion is properly set
I recommend you to print Response object from controller with dd() when unit testing this single test. This will show you where exactly the redirect is made. Unit test engine just parses the Response object and I believe the problem is in the code rather in the Unit test.
Could you check the test hitting the redirect function in controller
you can put a die in function and find-out where is the error
It should be an issue in method.
then check it actually goes in to the redirect part
for example you want to test a request validation failure. And for example it suppose to redirect your user to a previous url with validation failed exception and some messages. Say, you want to update a customer name.
// --- making a post request ----
$name = 'a new name that is too long';
$route = route('customer.update');
$response = $this
->withSession(['_previous' => ['url' => 'https://google.com']])
->call('POST', $route, [
'_token' => csrf_token(),
'name' => $name,
]);
// --- unit test assertions ----
$response->assertStatus(302);
$response->assertRedirect('https://google.com');
$message = __('#lang_file.some error message text here.');
$expected = [
'default' => ['' => [$message]]
];
$actual = session_errors()->getBags();
$actual = json_encode($actual);
$actual = json_decode($actual, true);
$this->assertSame($expected, $actual);
a function that return session errors:
function session_errors(): ViewErrorBag
{
$key = config('session.keys.errors');
$errors = session()->get($key, app(ViewErrorBag::class));
return $errors;
}

Exception handling with phpunit and Silex

I am using a route in Silex to delete an object from the database. If an object does not exist, a 404 error should be thrown. This works fine in the browser, and the response is received accordingly.
This is my source:
$app->delete("/{entity}/{id}", function(\Silex\Application $app, HttpFoundation\Request $request, $entity, $id) {
// some prep code is here
$deleteObject = $this->em->getRepository($entityClass)->find($id);
if (empty($deleteObject))
$app->abort(404, "$ucEntity with ID $id not found");
// other code comes here...
}
This is my test case:
// deleting the same object again should not work
$client->request("DELETE", "/ccrud/channel/$id");
$this->assertTrue($response->getStatusCode() == 404);
Now phpunit fails with the following error:
1) CrudEntityTest::testDelete
Symfony\Component\HttpKernel\Exception\HttpException: Channel with ID 93 not found
I can see from the message that the 404 was thrown, but I cannot test the response object as planned. I know that in theory I could assert for the exception itself, but that is not what I want to do, I want to get the response (as a browser would do as well) and test for the status code itself.
Anybody any ideas how to reach that or if there is a better way to test this?
Thanks,
Daniel
This is how it is being done in the tests of Silex itself (see here):
public function testErrorHandlerNotFoundNoDebug()
{
$app = new Application();
$app['debug'] = false;
$request = Request::create('/foo');
$response = $app->handle($request);
$this->assertContains('<title>Sorry, the page you are looking for could not be found.</title>', $response->getContent());
$this->assertEquals(404, $response->getStatusCode());
}

Categories