How can I test that I get this message:
public function doSomething()
{
if($ok){
return view('message-page')
->with('title','The message?')
}
}
What can I assert to check the message that is passed to the view?
Updated:
Laravel 5.5
I think it is tricky because I am not doing a HTTP call, which would return a response. I am just doing a function call ($foo->doSomething();), so I don't get a response returned.
I can't do a GET because I need to pass in a mocked object. Here is my test so far:
public function do_the_test()
{
//Arrange
$mockObject = Mockery::mock('App\MockObject');
$authCode = 123456;
$mockObject->shouldReceive('doSomething')->once()->with($authCode)->andReturn([
'code' => '123456',
'name' => 'joe',
'username' => 'smith',
'email' => 'joe#yahoo.co.uk',
'domain' => 'yahoo.co.uk'
]);
//Act
$object = new Foo($mockObject);
$object->doSomething();
//Assert
??
//check that view is returned with message text
}
Progress:
I have hacked this by setting a session variable (instead of passing the messages with the view) and then checking that with assertEquals();
Would be nice to find a better way.
Not sure which laravel version you have, but still, you can use assertViewHas($key, $value) function:
public function testViewDoSomethingHasCorrectTitle()
{
$response = $this->call('GET', '/my_route');
$this->assertViewHas('title', 'The message?')
}
https://laravel.com/docs/5.5/http-tests#assert-view-has
just do a dd() and you'll know if it passed to the controller.
In your view do a {{ dd($title) }}
Related
I am writing test for some controller method that will validate request data and create document by 3rd party API. Then it should return response with 201 status code. I am using mocking to mock my service class that is creating document. Here is my controller:
public function margin(MarginRequest $request){
$data = $request->validated();
$fileId = $this->documentService->createDocument(auth()->user()->id, SignableDocumentAbstract::MARGIN_CERTIFICATE, new MarginDocument(), $data);
return response()->json([
'code' => 201,
'message' => 'Margin agreement generated successfully',
'data' => [
'uuid' => $fileId
]
], 201);
}
And my test:
public function test_public_margin()
{
$marginData = (new MarginFaker())->fake();
Auth::shouldReceive('user')->once()->andReturn((object)['id' => 999999]);
$this->mock(DocumentService::class, function (MockInterface $mock) {
$mock->shouldReceive('createDocument')
->once()
->andReturn(Str::random());
});
$request = MarginRequest::create('/api/public/documents/margin', 'POST', $marginData);
$response = app(PublicController::class)->margin($request);
$this->assertEquals(201, $response->getStatusCode());
}
Everything look OK but when I run my test it throws error that
Call to a member function validated() on null
It is given in $data = $request->validated(); line of controller. But I can't understand why my $request is recognized as null. Even if I dump request object by dump($request) I can see that it is object and holds all required fields inside.
Then what can be the reason, why I can't call validated() method while testing?
You do not test like that when you want to test a URL. You NEVER mock a controller or do new Controller and call a method inside it.
You have to read the HTTP Test section of the documentation.
So, your test should look like this:
public function test_public_margin()
{
$this->actingAs(User::factory()->create());
$this->mock(DocumentService::class, function (MockInterface $mock) {
$mock->shouldReceive('createDocument')
->once()
->andReturn(Str::uuid());
});
$response = $this->post(
'/api/public/documents/margin',
['pass the needed data as an array, so the validation passes']
);
$response->assertStatus(201);
}
I got a function that returns a redirect with a success message as follows:
public function update(Request $request)
{
// ...
return redirect()->route('my.route')
->with('success', 'All objects have been updated.');
}
I wrote a feature test to test the functionality of this function.
public function test_user_can_update()
{
$response = $this->put(route('my.route'), [
'value' => 15
]);
$response->assertStatus(302);
$response->assertSessionHasNoErrors();
$this->assertEquals('All objects have been updated.', $response->getSession()->get('success'));
}
Is there an easier way to access the session and check the success message?
I found two easier ways to check the success message:
$response->assertSessionHas('success', 'Ă„nderungen gespeichert.');
// or
$this->assertEquals(session('success'), 'Ă„nderungen gespeichert.');
I have a problem with the laravel validation.
Call to a member function fails() on array
Symfony\Component\Debug\Exception\FatalThrowableError thrown with message "Call to a member function fails() on array"
Stacktrace:
`#0 Symfony\Component\Debug\Exception\FatalThrowableError in
C:\laragon\www\frontine\app\Http\Controllers\authController.php:37
public function postRegister(Request $request)
{
$query = $this->validate($request, [
'user' => 'string|required|unique:users|min:4|max:24',
'email' => 'email|string|required|unique:users',
'pass' => 'string|required|min:8',
'cpass' => 'string|required|min:8|same:pass',
'avatar' => 'image|mimes:jpeg,jpg,png|max:2048',
]);
if ($query->fails())
{
return redirect('/registrar')
->withErrors($query)
->withInput();
}
}
The error is because what the ->validate() method returns an array with the validated data when applied on the Request class. You, on the other hand, are using the ->fails() method, that is used when creating validators manually.
From the documentation:
Manually Creating Validators
If you do not want to use the validate method on the request, you may
create a validator instance manually using the Validator facade. The
make method on the facade generates a new validator instance:
use Validator; // <------
use Illuminate\Http\Request;
class PostController extends Controller
{
public function store(Request $request)
{
$validator = Validator::make($request->all(), [ // <---
'title' => 'required|unique:posts|max:255',
'body' => 'required',
]);
if ($validator->fails()) {
return redirect('post/create')
->withErrors($validator)
->withInput();
}
// Store the blog post...
}
}
The ->fails() is called in the response of the Validator::make([...]) method that return a Validator instance. This class has the fails() method to be used when you try to handled the error response manually.
On the other hand, if you use the validate() method on the $request object the result will be an array containing the validated data in case the validation passes, or it will handle the error and add the error details to your response to be displayed in your view for example:
public function store(Request $request)
{
$validatedData = $request->validate([
'attribute' => 'your|rules',
]);
// I passed!
}
Laravel will handled the validation error automatically:
As you can see, we pass the desired validation rules into the validate
method. Again, if the validation fails, the proper response will
automatically be generated. If the validation passes, our controller
will continue executing normally.
What this error is telling you is that by doing $query->fails you're calling a method fails() on something (i.e. $query) that's not an object, but an array. As stated in the documentation $this->validate() returns an array of errors.
To me it looks like you've mixed a bit of the example code on validation hooks into your code.
If the validation rules pass, your code will keep executing normally;
however, 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, [...]
-Laravel Docs
The following code should do the trick. You then only have to display the errors in your view. You can read all about that, you guessed it, in... the docs.
public function postRegister(Request $request)
{
$query = $request->validate($request, [
'user' => 'string|required|unique:users|min:4|max:24',
'email' => 'email|string|required|unique:users',
'pass' => 'string|required|min:8',
'cpass' => 'string|required|min:8|same:pass',
'avatar' => 'image|mimes:jpeg,jpg,png|max:2048',
]);
}
I have a controller that after submitting a email, performs a redirect to the home, like this:
return Redirect::route('home')->with("message", "Ok!");
I am writing the tests for it, and I am not sure how to make phpunit to follow the redirect, to test the success message:
public function testMessageSucceeds() {
$crawler = $this->client->request('POST', '/contact', ['email' => 'test#test.com', 'message' => "lorem ipsum"]);
$this->assertResponseStatus(302);
$this->assertRedirectedToRoute('home');
$message = $crawler->filter('.success-message');
// Here it fails
$this->assertCount(1, $message);
}
If I substitute the code on the controller for this, and I remove the first 2 asserts, it works
Session::flash('message', 'Ok!');
return $this->makeView('staticPages.home');
But I would like to use the Redirect::route. Is there a way to make PHPUnit to follow the redirect?
You can get PHPUnit to follow redirects with:
Laravel >= 5.5.19:
$this->followingRedirects();
Laravel < 5.4.12:
$this->followRedirects();
Usage:
$response = $this->followingRedirects()
->post('/login', ['email' => 'john#example.com'])
->assertStatus(200);
Note: This needs to be set explicitly for each request.
For versions between these two:
See https://github.com/laravel/framework/issues/18016#issuecomment-322401713 for a workaround.
You can tell crawler to follow a redirect this way:
$crawler = $this->client->followRedirect();
so in your case that would be something like:
public function testMessageSucceeds() {
$this->client->request('POST', '/contact', ['email' => 'test#test.com', 'message' => "lorem ipsum"]);
$this->assertResponseStatus(302);
$this->assertRedirectedToRoute('home');
$crawler = $this->client->followRedirect();
$message = $crawler->filter('.success-message');
$this->assertCount(1, $message);
}
Laravel 8 tested
$response = $this->post'/contact', ['email' => 'test#test.com', 'message' => "lorem ipsum"]);
$response->assertStatus(302);
$response->assertRedirect('home');
$this->followRedirects($response)->assertSee('.success-message');
//or
$this->followRedirects($response)->assertSee('Ok!');
Worked for me, hoped it helps.
Since Laravel 5.5 to test redirect you can use assertRedirect:
/** #test */
public function store_creates_claim()
{
$response = $this->post(route('claims.store'), [
'first_name' => 'Joe',
]);
$response->assertRedirect(route('claims.index'));
}
//routes/web.php
Route::get('/', function () {
return redirect()->route('users.index');
})->name('index');
//on my TestClass
$response = $this->get('/');
$response->assertStatus(302);
$response->assertRedirect(route('users.index'));
For Laravel 5.6, you can set
$protected followRedirects = true;
within your class file for your test case
I'm having some trouble using Laravel's Input::replace() method to simulate a POST request during unit testing.
According to Jeffrey Way here and here, you can do something like this:
# app/tests/controllers/PostsControllerTest.php
public function testStore()
{
Input::replace($input = ['title' => 'My Title']);</p>
$this->mock
->shouldReceive('create')
->once()
->with($input);
$this->app->instance('Post', $this->mock);
$this->call('POST', 'posts');
$this->assertRedirectedToRoute('posts.index');
}
However, I can't get this to work. Input::all() and all Input::get() calls still return an empty array or null after Input::replace() is used.
This is my test function:
public function test_invalid_login()
{
// Make login attempt with invalid credentials
Input::replace($input = [
'email' => 'bad#email.com',
'password' => 'badpassword',
'remember' => true
]);
$this->mock->shouldReceive('logAttempt')
->once()
->with($input)
->andReturn(false);
$this->action('POST', 'SessionsController#postLogin');
// Should redirect back to login form with old input
$this->assertHasOldInput();
$this->assertRedirectedToAction('SessionsController#getLogin');
}
The $this->mock->shouldReceive() doesn't get called with $input though - it only gets an empty array. I've confirmed this in the debugger by looking at Input::all() and Input::get() for each value, and they're all empty.
TL/DR: How do I send a request with POST data in a Laravel unit test?
You should use Request::replace(), not Input::replace in order to replace input data for the current request.