How to share user data in laravel dusk tests - php

I'm very new to Laravel Dusk (like less than 24 hours) and I'm experimenting with creating some tests but I can't wrap my head around getting past the initial test.
So I have UserCanRegisterTest.php and UserCanSeeDashboardTest.php, In UserCanRegisterTest.php I register a user, how can I access that user info in UserCanSeeDashboardTest.php without having to recreate another user? I have tried researching but I've fallen down a rabbit hole, I've looked at memory, cookies, DatabaseTransactions but nothing seems to make sense or show an example.
Is it possible for me to use the $faker->safeEmail and $password from UserCanRegisterTest.php in UserCanSeeDashboardTest.php and all other tests I make?
UserCanRegisterTest.php:
<?php
namespace Tests\Browser;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;
class UserCanRegisterTest extends DuskTestCase
{
use DatabaseMigrations;
/*public function setUp()
{
parent::setUp();
$this->artisan('db:seed');
}*/
/** #test */
public function user_passes_registration_form()
{
$faker = \Faker\Factory::create();
/*$roleSeeder = new RoleTableSeeder();
$roleSeeder->run();
$permissionSeeder = new PermissionTableSeeder();
$permissionSeeder->run();*/
$this->browse(function($browser) use ($faker) {
$password = $faker->password(9);
$browser->visit('/register')
//->assertSee('Welcome Back!')
->type('company_name', $faker->company)
->type('name', $faker->name)
->type('email', $faker->safeEmail)
->type('password', $password)
->type('password_confirmation', $password)
->press('REGISTER')
->assertPathIs('/register');
});
}
}
Here is UserCanSeeDashboardTest.php (note how I'd like to use $faker->safeEmail and $password from the above test so I don't need to create new user every time).
<?php
namespace Tests\Browser;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;
use App\User;
class UserCanSeeDashboardTest extends DuskTestCase
{
use DatabaseMigrations;
/*public function setUp()
{
parent::setUp();
//$this->artisan('db:seed');
}*/
/** #test */
public function test_I_can_login_successfully()
{
$this->browse(function ($browser) {
//$user->roles()->attach(1); //Attach user admin role
$browser->visit('/login')
->type('email', $faker->safeEmail)
->type('password', $password)
->press('SIGN IN')
->assertSee('Dashboard');
});
}
}
Ideally, I have a test that registers a user, then I have other tests that use that registered user's data to log in and test other parts of my app.

PHPUnit doesn't have great support for tests that depend on each other. Tests in PHPUnit should mostly be considered independent. The framework does provide the #depends annotation that you might have been able to use for you tests that depend on the registration method, but it only works for tests that are in the same class.
Also, you don't need to worry about creating multiple users because you're using the DatabaseMigrations trait that refreshes your test database for you after every test.
The way I see it, you have two options. Either you:
Move your registration code (the part starting from $browser->visit('/register')) to a new method and then call that method in both your user_passes_registration_form test and in your other tests where you want to have a registered user, or
Write a new method that you can call from your other tests that registers a user directly in your database (e.g. using User::create).
The benefit of the second option is that you'll have less HTTP calls which will result in a faster test run and only your registration test would fail (instead of all your tests) when your registration endpoint is broken.
So what I'd suggest is that you keep your registration test as is and use either a trait or inheritance to add a few methods that you can reuse to register or login a test user from other test methods.
You could create a class MyDuskTestCase that inherits from DuskTestCase and that contains a method to register a test user:
<?php
namespace Tests;
use Tests\DuskTestCase;
use App\User;
use Hash;
abstract class MyDuskTestCase extends DuskTestCase
{
private $email = 'test#example.com';
private $password = 'password';
public function setup(): void
{
parent::setUp();
// If you want to run registerTestUser for every test:
// registerTestUser();
}
public function registerTestUser()
{
User::create([
'email' => $this->email,
'name' => 'My name',
'password' => Hash::make($this->password)
]);
// assign a role, etc.
}
public function getTestUser()
{
return User::where('email', $this->email)->first();
}
}
Then you can either run the registerTestUser method in the setup method to create the test user for every test, or you can call the method from only the tests where you'll need the user. For example:
<?php
namespace Tests\Browser;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;
use Tests\MyDuskTestCase;
class UserCanRegisterTest extends MyDuskTestCase
{
use DatabaseMigrations;
public function test_I_can_login_successfully()
{
$this->registerTestUser();
$this->browse(function ($browser) use ($user) {
$browser->visit('/login')
->type('email', $this->email)
->type('password', $this->password)
->press('SIGN IN')
->assertSee('Dashboard');
});
}
}
For logins, you can either add another method to your base test class to log the test user in, or you could use the loginAs method that Dusk provides:
$user = this->getTestUser();
$this->browse(function ($browser) {
$browser->loginAs($user)
->visit('/home');
});

Related

PHPUnit: How to test Doctrine Repository method that returns void?

I have a Doctrine repository class that can be used to persist a User, I don't want to check if the entity was really persisted or not, I just want to know what to do in this situation:
It's easy to test a repository method that has a return value. But in this case, I have nothing to do, and I'd like to cover 100% of the code, without making unsafe code that can break like use #addToAssertionCount.
<?php
namespace Domain\Repository;
use DateTime;
use Domain\Entity\User;
use Domain\Repository\Interfaces\UserRepositoryInterface;
class UserRepository extends Repository implements UserRepositoryInterface
{
public function create(User $user): void
{
$user->setCreatedAt(new DateTime('now'));
$this->getEntityManager()->persist($user);
}
}
And a testing class for it:
<?php
namespace Domain\Repository;
use Doctrine\ORM\EntityManager;
use Domain\Entity\User;
use PHPUnit\Framework\TestCase;
class UserRepositoryTest extends TestCase
{
private UserRepository $sut;
public function setUp(): void
{
$entity_manager = $this->createMock(EntityManager::class);
$this->sut = new UserRepository($entity_manager);
}
public function test_assert_create(): void
{
$user = $this->createMock(User::class);
$this->sut->create($user);
// What to assert??
}
}
At this point, I don't know even what to assert, once the persist() method returns void, which I can't mock.
Focusing on 100% code coverage is not a good idea and encourages writing tests that have little to no value. What does that mean? The create method has two side effects: It changes the users creation date and persists it. You could test it like this:
final class UserRepositoryTest extends TestCase
{
/**
* #var EntityManager&MockObject
*/
private EntityManager $entityManager;
private UserRepository $sut;
public function setUp(): void
{
$this->entityManager = $this->createMock(EntityManager::class);
$this->sut = new UserRepository($this->entityManager);
}
public function test_create_should_persist_entity(): void
{
$user = new User();
$user->setCreatedAt(new DateTime('2000-01-01 12:15:30'));
// validate that persist call was made
$this->entityManager->expects(self::once())
->method('persist')
->with($user);
$this->sut->create($user);
// validate that creation date was set
self::assertEqualsWithDelta(new DateTime('now'), $user->getCreatedAt(), 3);
}
}
You could go even one step further and verify that the creation date was set before the persist call was made by using the callback constraint. But then, you're pretty much checking the implementation line by line in your test. That's how people end with tests that break all the time.
So, what to do instead? Focus on the purpose of the user repository: If you put something in, you should be able to get it out. This, however, requires that you use an actual entity manager. But you wrote, that you don't want to check that the entity was actually persisted. In that case, I would rather write no tests than the example I gave above.

FOSUserBundle - Event set variable in session when success authentication?

I would like to know if it is possible to insert a variable in the session if authentication succeds (using with FOSUserBundle).
It is more or less than two lines to insert.
$session = $request->getSession();
$this->$session->set('type','OneType');
Is there a very simple way to do it? I really want to do it when there is successful authentication, not anywhere.
You need to listen to the event security.interactive_login. (Docs)
Simple example, using an event subscriber:
<?php
// src/EventSubscriber/SecuritySubscriber.php
namespace App\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Http\SecurityEvents;
class SecuritySubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
SecurityEvents::INTERACTIVE_LOGIN => 'successfulLogin',
];
}
public function successfulLogin( InteractiveLoginEvent $event )
{
$event->getRequest()->getSession()->set('foo', 'bar');
}
}
You haven't specified version, but this should work on a default installation for Symfony 4.

undefined action() in phpunit

<?php
namespace Tests\Feature;
use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;
class MemberRegTest extends TestCase
{
/**
* A basic test example.
*
* #return void
*/
public function testExample()
{
$response = $this->call('GET', '/addmember');
$response = $this->action('GET', 'MemberController#addmember');
}
}
After testing gives me error
Error: Call to undefined method Tests\Feature\MemberRegTest::action()
What i do wrong?
There was an action method inside TestCase back to Laravel 4.x. This method has been replaced for new ones in different classes and packages. (You can confirm this reviewing the Laravel 5.6 TestCase class)
For the latest versions of Laravel, If you are trying to test a HTTP Request you could do:
$response = $this->json('GET', 'api/addmember');
$response->assertStatus(200) // or whatever you want to assert.
Now if you want to do browser tests, you should use the official Laravel Dusk. This package has very cool and useful methods to simulate user interactions with your site, as easy as this:
$this->browse(function ($browser) use ($user) {
$browser->visit('/login')
->type('email', $user->email)
->type('password', 'secret')
->press('Login')
->assertPathIs('/home');

Laravel Unit testing Eloquent insertion

I'm currently having some troubles in testing a function in Laravel. This function is a simple save user function.
The current structure involves a User
class User extends Authenticatable
Then I have a UserController
class UserController extends Controller
{
protected $user;
public function __construct(User $user)
{
$this->user = $user;
$this->middleware('admins');
}
The save function is defined on the UserController class, this class only assigns the request variables and uses Eloquent save function to save to database.
The function signature is the following:
public function storeUser($request)
{
$this->user->name = $request->name;
$this->user->email = $request->email;
$this->user->country_id = $request->country_id;
return $this->user->save();
}
The NewAccountRequest object extends from Request and has the validation rules for the request.
class NewAccountRequest extends Request
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'name' => 'required|max:255',
'email' => 'required|email|max:255|unique:user',
'password' => 'required|min:6|max:60',
];
}
}
My problem is how can I unit test this storeUser function.
I have the current test:
public function testSaveUserWithEmptyRequest()
{
$user = $this->createMock(User::class);
$controller = new UserController($user);
$request = $this->createMock(NewAccountRequest::class);
$store = $controller->storeUser($request);
$this->assertFalse($store);
}
I'm mocking both User and NewAccountRequest, the problem is that the assertion should be false, from the Eloquent save. Instead I'm getting Null. Any idea on how can I correctly test the function?
<?php
namespace Tests\Unit;
use Tests\TestCase;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class ExampleTest extends TestCase
{
use DatabaseTransactions; // Laravel will automatically roll back changes that happens in every test
public function testSaveUserWithEmptyRequest()
{
$user = new User();
$controller = new UserController($user);
$request = $this->createMock(NewAccountRequest::class);
$store = $controller->storeUser($request);
$this->assertFalse($store);
}
}
This is exactly what you are trying to do, but unfortunately this will fail due to database exceptions...
Mocking a request or even manually crafting it will not do the data input validation.. and in your example password field is not nullable and will cause PDOException: SQLSTATE[HY000]: General error: 1364 Field 'password' doesn't have a default value
The recommended way to test functions depending on request, is to use http test helpers provided by laravel like $response = $this->post('/user', ['name' => 'Sally']);
A much better approach is to use the repository design pattern.. this simply means collate your database functions into separate classes and call it from controllers ..

User Authentication in Unit Testing Laravel 5.1

I'm a bit confused about the unit testing in Laravel 5.1.
I want to test the updating of settings from a user account. I'm using phpunit for testing.
This is my test case.
/** #test */
public function it_updates_personal_details(){
$this->withoutMiddleware();
$this->visit('/account/settings')
->post('/account/settings', ["firstname"=>"RingoUpdated", 'lastname'=>"RingyUpdated", "username"=>"ringo", "bio"=>"My Personal Information", "facebook"=>"myFbUsername", "twitter"=>"myTwUsername", ])
->seeJson([
"signup"=>true
]);
}
But in my controller, I'm using Auth::id() to get the current logged in user's id. How can I mock this in Laravel 5.1 ?
The simplest way is to make use of Mockery in the Facade:
public function it_updates_personal_details(){
Auth::shouldReceive('id')->andReturn(1);
$this->withoutMiddleware();
$this->visit('/account/settings')
->post('/account/settings', ["firstname"=>"RingoUpdated", 'lastname'=>"RingyUpdated", "username"=>"ringo", "bio"=>"My Personal Information", "facebook"=>"myFbUsername", "twitter"=>"myTwUsername", ])
->seeJson([
"signup"=>true
]);
}
That’s awesome, we can move to class TestCase for all test case and we can give a name, like this:
public function loginWithFakeUser()
{
$user = new User([
'id' => 1,
'name' => 'yish'
]);
$this->be($user);
}
When we need to authenticated user we can call loginWithFakeUser to pretend user.
The actingAs helper method. Laravel 5.3
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use App\User; // user model
class ExampleTest extends TestCase
{
public function testApplication()
{
$user = User::find(1); // find specific user
$this->actingAs($user)
->visit('/')
->see('Hello, '.$user->name);
}
}
The actingAs helper method. provides a simple way to authenticate a given user as the current user.
You may also use a model factory to generate and authenticate a user:
$user = factory(App\User::class)->create();
$this->actingAs($user)
->visit('/')
->see('Hello, '.$user->name);

Categories