You need to either annotate the method "a_user_is_redirected_to_dashboard_if_logged_in_and_tries_to_access_login_page" or prepend it's name with the word "test" ... "test_a_user_is_redirected_to_dashboard_if_logged_in_and_tries_to_access_login_page"
So I did such and I still get the following just on that one test.
Me-iMac:backstage me$ phpunit --testsuite unit
PHPUnit 4.8.21 by Sebastian Bergmann and contributors.
E
Time: 30.19 seconds, Memory: 11.25Mb
There was 1 error:
1) ExampleTest::a_user_is_redirected_to_dashboard_if_logged_in_and_tries_to_access_login_page
PDOException: SQLSTATE[HY000] [2002] Operation timed out
/Users/me/Projects/app/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php:55
/Users/me/Projects/app/vendor/laravel/framework/src/Illuminate/Database/Connectors/MySqlConnector.php:22
/Users/me/Projects/app/bootstrap/cache/compiled.php:11423
/Users/me/Projects/app/bootstrap/cache/compiled.php:11419
/Users/me/Projects/app/bootstrap/cache/compiled.php:11326
/Users/me/Projects/app/bootstrap/cache/compiled.php:11281
/Users/me/Projects/app/bootstrap/cache/compiled.php:11383
/Users/me/Projects/app/tests/unit/ExampleTest.php:15
/Users/me/Projects/app/tests/unit/ExampleTest.php:15
FAILURES!
Tests: 1, Assertions: 0, Errors: 1.
@xtremer360 I forgot to copy&paste the /** @test */ annotation before the method name, that's why it failed.
BUT, looking through this whole thread again I stumbled across the fact that you are using homestead, I didn't realize that before. Are you executing the tests within homestead or in your "local" environment, i.e. where do you execute phpunit? It looks like you are executing phpunit in your local environment and that might fail to connect to the database within homestead. See this thread.
@skliche I am now inside my ssh by running the homestead up command in terminal.
I now how this issue.
Me-iMac:backstage me$ phpunit --testsuite unit
PHPUnit 4.8.21 by Sebastian Bergmann and contributors.
F
Time: 304 ms, Memory: 16.00Mb
There was 1 failure:
1) ExampleTest::a_user_is_redirected_to_dashboard_if_logged_in_and_tries_to_access_login_page
Did not land on expected page [http://localhost/app/dashboard].
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'http://localhost/app/dashboard'
+'http://localhost'
/Users/me/Projects/app/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/InteractsWithPages.php:142
/Users/me/Projects/app/tests/unit/ExampleTest.php:31
FAILURES!
Tests: 1, Assertions: 4, Failures: 1.
@xtremer360 Ok, so we solved the problem with the SQL timeouts.
What you are seeing now is that you are not being redirected to the page you expected. You expected to get redirected to app/dashboard but instead you have been redirect to /. Did you modify your RedirectIfAuthenticated middleware? It should contain the redirect call within the handle method.
@skliche I did not modify it. I didn't see anywhere in the documentation that explained needing to if this is a scenario.
@xtremer360 So, what does the redirect look like in your middleware?
Looking at github it looks like the default was changed from
return redirect('/home');
to
return redirect('/');
last November because "The route /home doesn’t exist in a default Laravel application, whereas / does."
If its return redirect('/'); in your middleware then you either need to change the redirect or your assertion in the test.
@skliche Okay how do I know what I need to change it to though. This is for an admin panel and I have lots of different pages that obviously can't be accessed by anyone who isn't authenticated so if they aren't I want them to be redirected to app/login. App is actually a route prefix that I have setup.
@xtremer360 But that is not what you are testing right there. That test is checking that someone who is logged in is not allowed to visit the login page. Just leave the middleware as is and change your assertion to:
$this->seePageIs('/');
But I don't want to test to see if they are at the homepage. What I am wanting to test for is the following.
- If they are authenticated and are trying to reach the login page then they are redirected to the dashboard since they wouldn't need to login again.
// Separate test not written as of yet.
- If they are NOT authenticated and they are trying to access the dashboard then they are redirected to login page.
Then change the redirect in the middleware as it currently redirects to /.
@skliche So how do I add the fact that there's a route group prefix?
return redirect(route('login'));
It's the RedirectIfAuthenticated middleware, you need the route to your dashboard, e.g.:
return redirect('/dashboard');
@skliche So I went back to my regular test with all my tests on it for my authentication and I get errors again. The error I'm getting is for the last 3 tests which all fail but the first one passes.
This is the error.
Caused by
PDOException: SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'john@example.com' for key 'users_email_unique'
<?php
use Illuminate\Foundation\Testing\DatabaseTransactions;
class AuthTest extends TestCase {
use DatabaseTransactions;
protected $user;
protected $password = 'testpass123';
/** @before */
public function setupUserObjectBeforeAnyTest()
{
$this->user = factory(App\User::class)->create([
'email' => 'john@example.com',
'password' => bcrypt($this->password),
]);
}
/** @test */
public function a_user_can_successfully_log_in()
{
$this->visit(route('login'))
->type($this->user->email, 'email')
->type($this->password, 'password')
->press('Login')
->seePageIs(route('dashboard'));
}
/** @test */
public function a_user_receives_errors_for_wrong_login_credentials()
{
$this->visit(route('login'))
->type($this->user->email, 'email')
->type('invalid-password', 'password')
->press('Login')
->see('These credentials do not match our records.');
}
/** @test */
public function a_user_is_redirected_to_dashboard_if_authenticated_and_tries_to_access_login_page()
{
$this->actingAs($this->user)
->visit(route('login'))
->seePageIs(route('dashboard'));
}
/** @test */
public function a_user_is_redirected_to_login_page_if_not_authenticated_and_tries_to_access_dashboard()
{
$this->visit(route('dashboard'))
->seePageIs(route('login'));
}
}
Maybe someone else will see why this is happening.
@xtremer360 The way phpunit works results in the setup method being called before the DatabaseTransactions trait's method has been called. Therefor the transaction gets initiated after you created the user record within your setup method. Initialize the transaction first thing in your setup method:
/** @before */
public function setupUserObjectBeforeAnyTest()
{
$this->app->make('db')->beginTransaction();
$this->beforeApplicationDestroyed(function () {
$this->app->make('db')->rollBack();
});
$this->user = factory(App\User::class)->create([
'email' => 'john@example.com',
'password' => bcrypt($this->password),
]);
}
Delete the existing record for john@example.com and it should work.
@skliche I guess I don't understand because why add that because why have those first 3 lines inside the function if I"m using DatabaseTransactions. Is there any alternative ways to handle this?
@xtremer360 What happens is the following:
First phpunit calls any setUp() method and methods annotated with @begin in your test class. In your case the method setupUserObjectBeforeAnyTest() gets called and it creates a user object that is persisted to the database. At this point no database transaction has been started.
Next phpunit calls the beginDatabaseTransaction() method in the DatabaseTransactions trait. This method begins a database transaction. But this is too late because you have already changed the database. The user you have created has been created outside the transaction. So the rollback of this transaction has no effect on the user you created.
The three lines above are exactly the same code that is used by the DatabaseTransactions trait. Placing them at the beginning of the method setupUserObjectBeforeAnyTest() makes sure that the database transaction is started before any modifications of the database have taken place.
This order of execution is a limitation imposed by phpunit. You can check it by placing an echo statement in each of the methods and have a look at the sequence.
I am not aware of any simple way to ensure that the DatabaseTransactions trait's method is called first. That's why I advised you to add the three lines.
@skliche I've just never seen @JeffreyWay apply those three lines in a test in any of his videos in his videos in the Testing Laravel video series.
@xtremer360 I don't recall any video where he needed to create a new object in the database for every single test so I've never seen him modify the database in a setUp or @before method.
What I've seen him doing is what we already discussed at the beginning of this thread: Create a method like your get_user() method and call it at the beginning of every single test method that needs a fresh user object.
@skliche I was thinking like this video.
https://laracasts.com/series/phpunit-testing-in-laravel/episodes/10
Basically for this Auth test I want two things to be made available.
- A new logged in user on a few tests in this class
- A new user that is NOT logged in on a few tests in this class.
For reasons of accessing pages that they can and cannot access because of being authenticated or not or even perhaps eventually not having the right role for.
The two tests I want to make sure are right before I drop this thread are:
- A user is logged in and they try to access the login page they are redirected to the dashboard.
- A user is not logged in and tries to access the dashboard page and they are redirected to the login page.
@xtremer360 Try it out. If you do what he is doing in that video you will see that all of the posts created in the setUp method still exist in your database. The use DatabaseTransactions; at the top doesn't cover the records created in the setUp method. I understand this is hard to get and I struggled a lot with this stuff.
Your options are:
1.) If you are not comfortable with the @begin-method then just don't use it. Create your user model within your test method and it will be covered by the database transaction. Make sure to include your $this->get_user(); at the beginning of every test function where you need the user object.
or
2.) Use the @begin-method with setting up the database transaction:
/** @before */
public function setupUserObjectBeforeAnyTest() {
$this->app->make('db')->beginTransaction();
$this->beforeApplicationDestroyed(function () {
$this->app->make('db')->rollBack();
});
$this->user = factory(App\User::class)->create([
'email' => 'john@example.com',
'password' => bcrypt($this->password),
]);
}
This makes sure a user object exists for every single test and the database transaction is set up early enough to cover it.
or
3.) If you want to use the @begin-method but you don't like setting up the database transaction there, do this instead:
/** @before */
public function setupUserObjectBeforeAnyTest() {
$this->user = factory(App\User::class)->create([
'email' => 'john@example.com',
'password' => bcrypt($this->password),
]);
$this->beforeApplicationDestroyed(function() {
if ($this->user) {
$this->user->delete();
}
});
}
This makes sure a user object exists for every single test and it is deleted when the test is shut down.
All of your posts have been helpful. Thank you.
Please or to participate in this conversation.