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);
Related
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');
});
I've been reading the documentation up and down now, still not sure what I'm doing wrong. In my opinion the documentation is very difficult to understand for a beginner.
Anyway, I'm trying to make something akin to the Auth::user() method, where it returns additional data about a logged in user that I will be needing for this application.
I have this helper class here:
namespace App\Helpers;
use Auth;
use Illuminate\Http\Request;
use App\Models\Grouping\User;
use App\Models\Grouping\Client;
use App\Models\Grouping\Rank;
class ClientUser {
public function __construct($request) {
$this->request = $request;
}
public function client() {
return Client::find($this->request->session()->get('client_id'));
}
public function auth() {
if (Auth::check()) {
// Get the client
$client = $this->client();
// Get the client's user
$user = $client->users()->find(Auth::user()['id']);
// Get the rank of the logged in user
$rank = Rank::find($user->pivot->rank_id);
return [
'user' => $user,
'rank' => $rank,
'client' => $client
];
}
return null;
}
}
This is responsible for doing what I described, returning additional data that I can't get through Auth::user(). Now I'm trying to register this class in the AuthServiceProvider
public function register()
{
// Register client auth
$request = $this->app->request;
$this->app->singleton(ClientUser::class, function ($app) {
return new ClientUser($request);
});
}
Now what I don't understand is how I'm supposed to make this globally accessible throughout my app like Auth::user() is.
The problem with just making "importing" it is that it needs the request object, which is why I'm passing it through the service container.
Now here's where I'm stuck. I'm not able to access app in my controller or anywhere, and I can't define a Facade because a Facade expects you to return a string of the bound service that it should "alias?"
Change your service provider like this :
$this->app->bind('client.user', function ($app) {
return new ClientUser($app->request);
});
Create another class extended from Illuminate\Support\Facades\Facade.
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
class ClientUserFacade extends Facade {
public static function getFacadeAccessor(){
return "client.user";
}
}
Add 'ClientUser => ClientUserFacade::class in alias key of app.php
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 ..
I'm creating a Laravel controller where a Random string generator interface gets injected to one of the methods. Then in AppServiceProvider I'm registering an implementation. This works fine.
The controller uses the random string as input to save data to the database. Since it's random, I can't test it (using MakesHttpRequests) like so:
$this->post('/api/v1/do_things', ['email' => $this->email])
->seeInDatabase('things', ['email' => $this->email, 'random' => 'abc123']);
because I don't know what 'abc123' will be when using the actual random generator. So I created another implementation of the Random interface that always returns 'abc123' so I could assert against that.
Question is: how do I bind to this fake generator at testing time? I tried to do
$this->app->bind('Random', 'TestableRandom');
right before the test, but it still uses the actual generator that I register in AppServiceProvider. Any ideas? Am I on the wrong track completely regarding how to test such a thing?
Thanks!
You have a couple options:
Use a conditional to bind the implementation:
class AppServiceProvider extends ServiceProvider {
public function register() {
if($this->app->runningUnitTests()) {
$this->app->bind('Random', 'TestableRandom');
} else {
$this->app->bind('Random', 'RealRandom');
}
}
}
Second option is to use a mock in your tests
public function test_my_controller () {
// Create a mock of the Random Interface
$mock = Mockery::mock(RandomInterface::class);
// Set our expectation for the methods that should be called
// and what is supposed to be returned
$mock->shouldReceive('someMethodName')->once()->andReturn('SomeNonRandomString');
// Tell laravel to use our mock when someone tries to resolve
// an instance of our interface
$this->app->instance(RandomInterface::class, $mock);
$this->post('/api/v1/do_things', ['email' => $this->email])
->seeInDatabase('things', [
'email' => $this->email,
'random' => 'SomeNonRandomString',
]);
}
If you decide to go with the mock route. Be sure to checkout the mockery documentation:
http://docs.mockery.io/en/latest/reference/expectations.html
From laracasts
class ApiClientTest extends TestCase
{
use HttpMockTrait;
private $apiClient;
public function setUp()
{
parent::setUp();
$this->setUpHttpMock();
$this->app->bind(ApiConfigurationInterface::class, FakeApiConfiguration::class);
$this->apiClient = $this->app->make(ApiClient::class);
}
/** #test */
public function example()
{
dd($this->apiClient);
}
}
results
App\ApiClient^ {#355
-apiConfiguration: Tests\FakeApiConfiguration^ {#356}
}
https://laracasts.com/discuss/channels/code-review/laravel-58-interface-binding-while-running-tests?page=1&replyId=581880
I am working in Laravel authentication login using socialite. Now I can able to save data of user from socialite. But now I am facing problem how to authenticate user from gmail, github.
After some research I understood that I need to create custom authentication. I googled but all are Laravel 4.1 topics. If any one work on this please provide your answers.
I already read following topics but I didn't got how to do it?
http://laravel.com/docs/5.1/authentication#social-authentication
http://laravel.com/docs/5.1/providers
http://laravel-recipes.com/recipes/115/using-your-own-authentication-driver
http://laravel.io/forum/11-04-2014-laravel-5-how-do-i-create-a-custom-auth-in-laravel-5
Update
public function handleProviderCallback() {
$user = Socialite::with('github')->user();
$email=$user->email;
$user_id=$user->id;
//$authUser = User::where('user_id',$user_id)->where('email', $email)->first();
$authUser = $this->findOrCreateUser($user);
if(Auth::login($authUser, true)) {
return Redirect::to('user/UserDashboard');
}
}
private function findOrCreateUser($user) {
if ($authUser = User::where('user_id',$user->id)->first()) {
return $authUser;
}
return User::create([
'user_id' => $user->id,
'name' => $user->nickname,
'email' => $user->email,
'avatar' => $user->avatar
]);
}
This answer is most suited for Laravel 5.1. Please take care if you
are in some other version. Also keep in mind that IMHO this is a rather advanced level in Laravel, and hence if you don't fully understand what you are doing, you may end up crashing your application. The solution is not end to end correct. This is just a general guideline of what you need to do in order for this to work.
Adding Custom Authentication Drivers In Laravel 5.1
Hint: Laravel documentation for this topic is here.
Hint2: The last link you mentioned is quite useful in my opinion. I learned all of this after reading that link.
http://laravel.io/forum/11-04-2014-laravel-5-how-do-i-create-a-custom-auth-in-laravel-5
Before we start, I would first like to describe the login flow which will help you understand the process. Laravel uses a driver to connect to the database to fetch your records. Two drivers come pre-bundled with laravel - eloquent & database. We want to create a third so that we can customize it to our needs.
Illuminate\Auth\Guard inside your vendor folder is the main file which has code for the user to log in and log out. And this file mainly uses two Contracts (or interfaces) that we need to override in order for our driver to work. From Laravel's own documentation read this:
The Illuminate\Contracts\Auth\UserProvider implementations are only
responsible for fetching a Illuminate\Contracts\Auth\Authenticatable
implementation out of a persistent storage system, such as MySQL,
Riak, etc. These two interfaces allow the Laravel authentication
mechanisms to continue functioning regardless of how the user data is
stored or what type of class is used to represent it.
So the idea is that for our driver to work we need to implement Illuminate\Contracts\Auth\UserProvider and Illuminate\Contracts\Auth\Authenticatable and tell Laravel to use these implementations instead of the defaults.
So let's begin.
Step 1:
Choose a name for your driver. I name mine socialite. Then in your config/auth.php, change the driver name to socialite. By doing this we just told laravel to use this driver for authentication instead of eloquent which is default.
Step 2:
In your app/Provider/AuthServiceProvider in the boot() method add the following lines:
Auth::extend('socialite', function($app) {
$provider = new SocialiteUserProvider();
return new AuthService($provider, App::make('session.store'));
});
What we did here is:
We first used Auth facade to define the socialite driver.
SocialiteUserProvider is an implementation of UserProvider.
AuthService is my extension of Guard class. The second parameter this class's constructor takes is the session which laravel uses to get and set sessions.
So we basically told Laravel to use our own implementation of Guard class instead of the default one.
Step 3:
Create SocialiteUserProvider. If you read the Laravel's documentation, you will understand what each of these methods should return. I have created the first method as a sample. As you can see, I use my UserService class to fetch results. You can fetch your own results however you want to fetch them. Then I created an User object out of it. This User class implements the Illuminate\Contracts\Auth\Authenticatable contract.
<?php
namespace App\Extensions;
use App\User;
use App\Services\UserService;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider;
class SocialiteUserProvider implements UserProvider
{
private $userService;
public function __construct(UserService $userService)
{
$this->userService = $userService;
}
public function retrieveById($identifier)
{
$result = $this->userService->getUserByEmail($identifier);
if(count($result) === 0)
{
$user = null;
}
else
{
$user = new User($result[0]);
}
return $user;
}
public function retrieveByToken($identifier, $token)
{
// Implement your own.
}
public function updateRememberToken(Authenticatable $user, $token)
{
// Implement your own.
}
public function retrieveByCredentials(array $credentials)
{
// Implement your own.
}
public function validateCredentials(Authenticatable $user, array $credentials)
{
// Implement your own.
}
}
Step 4:
Create User class which implements the Authenticatable. This class has to implement this interface because the Guard class will use this class to get values.
<?php
namespace App;
use Illuminate\Contracts\Auth\Authenticatable;
class User implements Authenticatable
{
protected $primaryKey = 'userEmail';
protected $attributes = [];
public function __construct(array $attributes)
{
$this->attributes = $attributes;
}
public function getUserAttributes()
{
return $this->attributes;
}
public function getAuthIdentifier()
{
return $this->attributes[$this->primaryKey];
}
public function getAuthPassword()
{
// Implement your own.
}
public function getRememberToken()
{
// Implement your own.
}
public function setRememberToken($value)
{
// Implement your own.
}
public function getRememberTokenName()
{
// Implement your own.
}
}
Step 5:
Finally create the AuthService class that will call the Guard methods. This is my own implementation. You can write your own as per your needs. What we have done here is extended the Guard class to implement two new functions which are self explanatory.
<?php
namespace App\Services;
use Illuminate\Auth\Guard;
class AuthService extends Guard
{
public function signin($email)
{
$credentials = array('email' => $email);
$this->fireAttemptEvent($credentials, false, true);
$this->lastAttempted = $user = $this->provider->retrieveById($email);
if($user !== null)
{
$this->login($user, false);
return true;
}
else
{
return false;
}
}
public function signout()
{
$this->clearUserDataFromStorage();
if(isset($this->events))
{
$this->events->fire('auth.logout', [$this->user()]);
}
$this->user = null;
$this->loggedOut = true;
}
}
Step 6: Bonus Step
Just to complete my answer, I will also explain the structure that UserService class expects. First lets understand what this class does. In our above steps we created everything to let laravel know how to use our authentication driver, instead of theirs. But we still haven't told laravel that how should it get the data. All we told laravel that if you call the userService->getUserByEmail($email) method, you will get your data. So now we simply have to implement this function.
E.g.1 You are using Eloquent.
public function getUserByEmail($email)
{
return UserModel::where('email', $email)->get();
}
E.g.2 You are using Fluent.
public function getUserByEmail($email)
{
return DB::table('myusertable')->where('email', '=', $email)->get();
}
Update: 19 Jun 2016
Thank you #skittles for pointing out that I have not clearly shown where the files should be placed. All the files are to be placed as per the namespace given. E.g. if the namespace is App\Extensions and the class name is SocialiteUserProvider then location of file is App\Extensions\SocialiteUserProvider.php. The App directory in laravel is the app folder.
Good tutorial for setting up laravel socialite here: https://mattstauffer.co/blog/using-github-authentication-for-login-with-laravel-socialite
Auth::login doesn't return a boolean value you can use attempt to do a Auth::attempt
if(Auth::login($authUser, true)) {
return Redirect::to('user/UserDashboard');
}
Follow the tutorial and do this, and just have middleware configured on the home route
$authUser = $this->findOrCreateUser($user);
Auth::login($authUser, true);
return Redirect::to('home');