PhpSpec return always null on mocks - php

I'm working with PhpSpec and for some reason when I mock my dependencies and call them the willReturn method of PhpSpec give me a null value instead of the value passed.
This is the method that I'm trying to describe
/**
* Register an User
*
* #param array $infoUser
* #return User
*/
public function register(array $infoUser)
{
$user = $this->user->create($infoUser);
$this->raise(new UserRegistered($user));
return $user;
}
My Spec
class BaseAuthSpec extends ObjectBehavior
{
function it_is_initializable()
{
$this->shouldHaveType('Core\Auth\BaseAuth');
}
function let(AuthManager $guard,UserAuthRepository $user)
{
$this->beConstructedWith($guard,$user);
}
function it_register_an_user(UserAuthRepository $useRepo)
{
$user = [
'username' => 'fabri',
'email' => 'test#test.com',
'password' => 'password',
'repeat_password' => 'password'
];
$userModel = new User($user);
// this line return null instead the $userModel
$useRepo->create($user)->shouldBeCalled()->willReturn($userModel);
$this->raise(new UserRegistered($userModel))->shouldReturn(null);
$this->register($user)->shouldReturn($userModel);
}
}
I'm stuck with this issue, any suggest will be appreciated.

Arguments are matched by name. The user repository passed to your let() method, is not the same as passed to the it_register_an_user() method. To fix your issue, simply give it the same name.
There're other issues in your spec.
It's not possible to mock nor stub a method on the class you're speccing. This is not going to work:
$this->raise(new UserRegistered($userModel))->shouldReturn(null);
I'm not sure what's going on in the raise() method, but you should deal with it properly in your example, so either stub or mock any collaborators (or leave them alone if there's no return values relevant to the current example).
Another thing is that you use mocks when what you really need is stubs. I'd rewrite your example to be:
class BaseAuthSpec extends ObjectBehavior
{
function let(AuthManager $guard, UserAuthRepository $userRepo)
{
$this->beConstructedWith($guard, $user);
}
function it_registers_a_user(UserAuthRepository $userRepo, User $userModel)
{
$user = [
'username' => 'fabri',
'email' => 'test#test.com',
'password' => 'password',
'repeat_password' => 'password'
];
$userRepo->create($user)->willReturn($userModel);
$this->register($user)->shouldReturn($userModel);
}
}
The raise method should be covered by seperate examples.

Related

Can I put and retrieve the user information to and from session into a Laravel custom user provider?

I am not so into PHP and Laravel and I have the following problem, I came from Java.
I am following this tutorial to implement a custom user provider:
https://blog.georgebuckingham.com/laravel-52-auth-custom-user-providers-drivers/
I am using Laravel 5.3 version.
I briefly expain what I need: my Laravel application is only a front end application, all the business logic, included the user authentication, is performed by a Java back end application that exposes REST web services.
Performing a call to:
http://localhost:8080/Extranet/login
and passing username and password as basic authentication I obtain a JSON response like this that represent the logged user:
{
"userName": "Painkiller",
"email": "painkiller#gmail.com",
"enabled": true
}
So, in my Laravel application, I have to perform this call and then parse the previous returned JSON object to generate the authenticated object into the front end application session.
To do this I have implemented the previous tutorial (and it seems to works) implementing this custom user provider class named UserProvider that implements the Laravel IlluminateUserProvider interface:
<?php
namespace App\Authentication;
use Illuminate\Auth\GenericUser;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider as IlluminateUserProvider;
use GuzzleHttp\Client;
use function GuzzleHttp\json_encode;
use function GuzzleHttp\json_decode;
use Illuminate\Support\Facades\Log;
class UserProvider implements IlluminateUserProvider
{
public function retrieveById($identifier)
{
// TODO: Implement retrieveById() method.
\Log::info('retrieveById START');
// PERFORM THE CALL TO MY BACK END WB SERVICE AND CREATE A NEW GenericUser USING THESE INFORMATION:
$attributes = array(
'id' => 123,
'username' => 'nobili.andrea#gmail.com',
'password' => \Hash::make('SuperSecret'),
'name' => 'Dummy User',
);
$user = new GenericUser($attributes);
return $user;
}
public function retrieveByToken($identifier, $token)
{
// TODO: Implement retrieveByToken() method.
\Log::info('retrieveByToken START');
}
public function updateRememberToken(Authenticatable $user, $token)
{
// TODO: Implement updateRememberToken() method.
\Log::info('updateRememberToken START');
}
public function retrieveByCredentials(array $credentials) {
// TODO: Implement retrieveByCredentials() method.
\Log::info('retrieveByCredentials START');
\Log::info('INSERTED USER CREDENTIAL: '.$credentials['email'] . ' ' .$credentials['password']);
$client = new Client(); //GuzzleHttp\Client
$response = $client->get('http://localhost:8080/Extranet/login',
[
'auth' => [
'nobili.andrea#gmail.com',
'pswd'
]
]);
$dettagliLogin = json_decode($response->getBody());
\Log::info('response: '.(json_encode($dettagliLogin)));
//$user = new User('Pippo', 'pippo#google.com', true);
$attributes = array(
'id' => 123,
'username' => 'nobili.andrea#gmail.com',
'password' => \Hash::make('SuperSecret'),
'name' => 'Dummy User',
);
$user = new GenericUser($attributes);
\Log::info('USER: '.(json_encode($user)));
return $user;
}
public function validateCredentials(Authenticatable $user, array $credentials)
{
// TODO: Implement validateCredentials() method.
\Log::info('validateCredentials START');
return true;
}
}
This is only a preliminary test so the returned data are mocked.
It works in this way:
1) When the user insert his credential in the login page (http://localhost:8000/login) it is called the retrieveByCredentials() method:
public function retrieveByCredentials(array $credentials) {
// TODO: Implement retrieveByCredentials() method.
\Log::info('retrieveByCredentials START');
\Log::info('INSERTED USER CREDENTIAL: '.$credentials['email'] . ' ' .$credentials['password']);
$client = new Client(); //GuzzleHttp\Client
$response = $client->get('http://localhost:8080/Extranet/login',
[
'auth' => [
'nobili.andrea#gmail.com',
'pswd'
]
]);
$dettagliLogin = json_decode($response->getBody());
\Log::info('response: '.(json_encode($dettagliLogin)));
//$user = new User('Pippo', 'pippo#google.com', true);
$attributes = array(
'id' => 123,
'username' => 'nobili.andrea#gmail.com',
'password' => \Hash::make('SuperSecret'),
'name' => 'Dummy User',
);
$user = new GenericUser($attributes);
\Log::info('USER: '.(json_encode($user)));
return $user;
}
that performs a web service call to obtain the user information related to this user. Then these credential are verified by the validateCredentials() method (at this time it returns true every time). Finnaly it returns a GenericUser objct containing the information of the logged user (at this time are mocked because is a test and I have not yet paresed the JSON reeturned by my web service.
Then, when the user access to the next page (after the success login) it seems to me that is called the retrieveById($identifier) method, this:
public function retrieveById($identifier)
{
// TODO: Implement retrieveById() method.
\Log::info('retrieveById START');
// PERFORM THE CALL TO MY BACK END WB SERVICE AND CREATE A NEW GenericUser USING THESE INFORMATION:
$attributes = array(
'id' => 123,
'username' => 'nobili.andrea#gmail.com',
'password' => \Hash::make('SuperSecret'),
'name' => 'Dummy User',
);
$user = new GenericUser($attributes);
return $user;
}
At this time the logic is that it use the id of the previous logged user to perform a call to the back end web service, obtain again these information and create the same GenericUser object that will be returned to the next page that uses it. Now I have mock these userinformation.
Ok, this works but I can't do in this way for security reason.
So my idea is: when I retrieve the user information in the retrieveByCredentials(array $credentials), after have check that are correct, I will put this GenericUser object into session.
Then in the retrieveById() method I will retrieve these information from the session.
Can I do something like this? Could be a smart way? How can I put and retrieve an object\data into and from the session (I am not into PHP and front end).
Yes, you can.
When you log in a user using Laravel's authentication API, it is automatically stored in the application's session. So what you are doing is completely fine.
And you don't need to worry about methods like retrieveByCredentials fireing calls to your REST API when you get the user instance through Auth::user(). Every time this method is called, it checks if there is an user already loged in. If there is, it just returns the user, otherwise, it makes the necessary calls to find that user.
But I would suggest putting those calls to your REST API in another class. The UserProvider has only the responsibility of showing Laravel how to retrieve a user, so it should make use of delegation to call other objects to do the dirty work of finding something somewhere.
You can checkout more about how the session Auth class work in the source of laravel:
https://github.com/laravel/framework/blob/5.4/src/Illuminate/Auth/SessionGuard.php
This is how I solved it. It needs improvements yet but at least can be useful as a general guideline.
Notes: I'm using PHP 8 and taking advantage of constructor property promotion, so maybe you'll have to assign the instance variables. I'm also using Laravel 8.
Implement your API gateway that implements the method:
function authenticate(string $username, string $password): ?APIUser {}
Create an API user model:
<?php
namespace App\Auth;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Facades\Hash;
final class APIUser implements Authenticatable
{
function __construct(
private $id,
private $username,
private $password,
private $access_token,
private $remember_token,
private $expires_in,
private $platform_id,
) {
$this->password = Hash::make($this->password);
}
function getUsername()
{
return $this->username;
}
function getAccessToken()
{
return $this->access_token;
}
function getExpiresIn()
{
return $this->expires_in;
}
function getPlatformId()
{
return $this->platform_id;
}
/**
*
* Authenticatable section
*
*/
/**
* Get the name of the unique identifier for the user.
* #return string
*/
public function getAuthIdentifierName()
{
return 'id';
}
/**
* Get the unique identifier for the user.
* #return mixed
*/
public function getAuthIdentifier()
{
return $this->id;
}
/**
* Get the password for the user.
* #return string
*/
public function getAuthPassword()
{
return $this->password;
}
/**
* Get the token value for the "remember me" session.
* #return string
*/
public function getRememberToken()
{
$this->remember_token;
}
/**
* Set the token value for the "remember me" session.
* #param string $value
* #return void
*/
public function setRememberToken($value)
{
$this->remember_token = $value;
}
/**
* Get the column name for the "remember me" token.
*
* #return string
*/
public function getRememberTokenName()
{
return 'remember_token';
}
}
Create the user provider:
<?php
namespace App\Auth;
use App\Ports\AppAPIGateway;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Contracts\Session\Session;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
class APIUserProvider implements UserProvider
{
function __construct(
private AppAPIGateway $api,
private Session $session
) {
}
public function retrieveById($identifier)
{
if ($this->session->has('user')) {
return $this->session->get('user');
}
return;
}
public function retrieveByToken($identifier, $token)
{
Log::info('APIUserProvider: retrieveByToken (not implemented)');
return null;
}
public function updateRememberToken(Authenticatable $user, $token)
{
$user->setRememberToken($token);
}
public function retrieveByCredentials(array $credentials)
{
if (
!array_key_exists('username', $credentials)
||
!array_key_exists('password', $credentials)
) {
throw new AuthenticationException('Could not retrieve credentials: missing parameters');
}
$user = $this->api->authenticate(
$credentials['username'],
$credentials['password']
);
$this->session->put('user', $user);
$this->session->save();
return $user;
}
public function validateCredentials(Authenticatable $user, array $credentials)
{
if (
!array_key_exists('username', $credentials)
||
!array_key_exists('password', $credentials)
) {
throw new AuthorizationException('Could not validate credentials: missing parameters');
}
return
$user->getUsername() == $credentials['username']
&& Hash::check($credentials['password'], $user->getAuthPassword());
}
}
Modify app/Providers/AuthServiceProvider.php:
public function boot()
{
$this->registerPolicies();
Auth::provider('api_driver', function ($app, array $config) {
return new APIUserProvider(
$app->make('App\Ports\AppAPIGateway'),
$app->make('Illuminate\Contracts\Session\Session')
);
});
}
Enable this new provider in config/auth.php:
'defaults' => [
'guard' => 'api_guard',
'passwords' => 'api_provider',
],
...
'guards' => [
'api_guard' => [
'driver' => 'session',
'provider' => 'api_provider',
],
],
...
'providers' => [
'api_provider' => [
'driver' => 'api_driver',
],
],
When you did do
$user = new GenericUser($attributes);
You can follow with
Auth::login($user);
Now all your User attributes are stored in session and can be retrieved by calling
$user = Auth::user();

Laravel 5.1 Mockery expectation ignored while testing controller

I have a Controller depending on a UserManager. This is the controller constructor:
public function __construct(UserManager $manager) {
$this->manager = $manager;
}
This is the test code.
public function test_something() {
$this->withoutMiddleware();
// Setup Input.
$user = ['email' => 'foo#bar.baz', 'password' => 'pass', 'accessLevel' => 'admin'];
// Setup expectations.
$mock = \Mockery::mock("Users\UserManager")->shouldReceive('foo');
// Bind to container... not sure whether this is needed.
$this->app->instance("Users\UserManager", $mock);
// Call action.
$this->call('POST', 'api/v1/temp/users', ['user' => $user]);
}
I set the expectation on the foo method, which doesn't exists and therefore is not invoked anywhere, however my test won't fail.
Why?
You need to specify how many times the foo method should be called:
->shouldReceive('foo')->once();
Make sure that you also have a tearDown method where you reset Mockery or it won't work:
public function tearDown()
{
Mockery::close();
}

Laravel 4 - Hardcoded authentication

I want to create authentication mechanism without need for database where only one person (admin) who knows right username and password (which I would hardcode) would be able to login. I still want to use Auth::attempt(), Auth::check() and other functions.
I found out that I could create my own User driver, but it seems to me that there should be something simpler.
Maybe it is not very nice solution, but I want as simple as possible website.
It may only seem there should be something simpler, but in fact that's as simple as you can get if you want to extend the authentication system. All the methods you're using through the Auth facade (like attempt, check, etc.), are implemented within the Illuminate\Auth\Guard class. This class needs a UserProviderInterface implementation to be injected into the constructor in order to work. Which means that in order to use the Auth facade you either need to use the already implemented DatabaseUserProvider or EloquentUserProvider, or implement your own provider that handles the simple login you want.
Although the article you linked to may look lengthy, to achieve what you need you might get away with much less code in the provider than you might think. Here's what I think is what you need:
1. In your app/config/auth.php change the driver to simple and append the desired login credentials:
'driver' => 'simple',
'credentials' => array(
'email' => 'user#email.com',
'password' => 'yourpassword'
)
2. Create a file in your app directory called SimpleUserProvider.php that has this code:
use Illuminate\Auth\UserInterface;
use Illuminate\Auth\GenericUser;
use Illuminate\Auth\UserProviderInterface;
class SimpleUserProvider implements UserProviderInterface {
protected $user;
public function __construct(array $credentials)
{
$this->user = new GenericUser(array_merge($credentials, array('id' => null)));
}
// If you only need to login via credentials the following 3 methods
// don't need to be implemented, they just need to be defined
public function retrieveById($identifier) { }
public function retrieveByToken($identifier, $token) { }
public function updateRememberToken(UserInterface $user, $token) { }
public function retrieveByCredentials(array $credentials)
{
return $this->user;
}
public function validateCredentials(UserInterface $user, array $credentials)
{
return $credentials['email'] == $user->email && $credentials['password'] == $user->password;
}
}
3. Lastly you'll need to register the new provider with the authentication system. You can append this to the app/start/global.php file:
Auth::extend('simple', function($app)
{
return new SimpleUserProvider($app['config']['auth.credentials']);
});
This should give you a simple (no database) user authentication while still being able to use Laravel's facades.
The accepted answer did not work for me. Every time I logged in, the login was successful but when on the /home page, I was redirected to the login page again.
I found out that this was due to the user not being stored in the session as authenticated user. To fix this, I had to implement the getAuthIdentifier method in the User model class and also implement the retrieveById method .
I've also adjusted my solution to support multiple hard coded users (it presumes, that the email is unique, so we can also use it as id for the user):
1. In app/config/auth.php:
'providers' => [
'users' => [
'driver' => 'array',
],
],
'credentials' => [
'userA#email.com' => 'passA',
'userB#email.com' => 'passB',
]
2. The UserProvider:
use \Illuminate\Auth\GenericUser;
use \Illuminate\Contracts\Auth\UserProvider;
use \Illuminate\Contracts\Auth\Authenticatable;
class ArrayUserProvider implements UserProvider
{
private $credential_store;
public function __construct(array $credentials_array)
{
$this->credential_store = $credentials_array;
}
// IMPORTANT: Also implement this method!
public function retrieveById($identifier) {
$username = $identifier;
$password = $this->credential_store[$username];
return new User([
'email' => $username,
'password' => $password,
]);
}
public function retrieveByToken($identifier, $token) { }
public function updateRememberToken(Authenticatable $user, $token) { }
public function retrieveByCredentials(array $credentials)
{
$username = $credentials['email'];
// Check if user even exists
if (!isset($this->credential_store[$username])) {
return null;
}
$password = $this->credential_store[$username];
return new GenericUser([
'email' => $username,
'password' => $password,
'id' => null,
]);
}
public function validateCredentials(Authenticatable $user, array $credentials)
{
return $credentials['email'] == $user->email && $credentials['password'] == $user->getAuthPassword();
}
}
3. And in app/Providers/AuthServiceProvider:
use Illuminate\Support\Facades\Auth;
class AuthServiceProvider extends ServiceProvider
{
...
/**
* Register any authentication / authorization services.
*
* #return void
*/
public function boot()
{
Auth::provider('array', function($app, array $config) {
// Return an instance of Illuminate\Contracts\Auth\UserProvider...
return new ArrayUserProvider($app['config']['auth.credentials']);
});
}
}
4. In User.php (model):
class User extends Authenticatable
{
...
public function getAuthIdentifier()
{
return $this->email;
}
}
More Information:
For everyone who is interested, why there need to be the above stated additions:
When you login, the method login in Illuminate\Auth\SessionGuard is called. In this method you will find, that the identifier of the user is stored in the session with $this->updateSession($user->getAuthIdentifier()). Therefore we need to implement this method in our user model.
When you add $this->middleware('auth') in your controller, then the method authenticate() in Illuminate\Auth\Middleware\Authenticate is called. This again calls $this->auth->guard($guard)->check() to check, whether a user is authenticated. The check() method only tests, that there exists a user in the session (see Illuminate\Auth\GuardHelpers). It does this by calling the user() method of the guard, which in this case is again SessionGuard. In the user() method, the user is retrieved by taking the id stored in the session and calling retrieveById to get the user.

Laravel & Mockery: Unit testing the update method without hitting the database

Alright so I'm pretty new to both unit testing, mockery and laravel. I'm trying to unit test my resource controller, but I'm stuck at the update function. Not sure if I'm doing something wrong or just thinking wrong.
Here's my controller:
class BooksController extends \BaseController {
// Change template.
protected $books;
public function __construct(Book $books)
{
$this->books = $books;
}
/**
* Store a newly created book in storage.
*
* #return Response
*/
public function store()
{
$data = Input::except(array('_token'));
$validator = Validator::make($data, Book::$rules);
if($validator->fails())
{
return Redirect::route('books.create')
->withErrors($validator->errors())
->withInput();
}
$this->books->create($data);
return Redirect::route('books.index');
}
/**
* Update the specified book in storage.
*
* #param int $id
* #return Response
*/
public function update($id)
{
$book = $this->books->findOrFail($id);
$data = Input::except(array('_token', '_method'));
$validator = Validator::make($data, Book::$rules);
if($validator->fails())
{
// Change template.
return Redirect::route('books.edit', $id)->withErrors($validator->errors())->withInput();
}
$book->update($data);
return Redirect::route('books.show', $id);
}
}
And here are my tests:
public function testStore()
{
// Add title to Input to pass validation.
Input::replace(array('title' => 'asd', 'content' => ''));
// Use the mock object to avoid database hitting.
$this->mock
->shouldReceive('create')
->once()
->andReturn('truthy');
// Pass along input to the store function.
$this->action('POST', 'books.store', null, Input::all());
$this->assertRedirectedTo('books');
}
public function testUpdate()
{
Input::replace(array('title' => 'Test', 'content' => 'new content'));
$this->mock->shouldReceive('findOrFail')->once()->andReturn(new Book());
$this->mock->shouldReceive('update')->once()->andReturn('truthy');
$this->action('PUT', 'books.update', 1, Input::all());
$this->assertRedirectedTo('books/1');
}
The issue is, when I do it like this, I get Mockery\Exception\InvalidCountException: Method update() from Mockery_0_Book should be called exactly 1 times but called 0 times. because of the $book->update($data) in my controller. If I were to change it to $this->books->update($data), it would be mocked properly and the database wouldn't be touched, but it would update all my records when using the function from frontend.
I guess I simply just want to know how to mock the $book-object properly.
Am I clear enough? Let me know otherwise. Thanks!
Try mocking out the findOrFail method not to return a new Book, but to return a mock object instead that has an update method on it.
$mockBook = Mockery::mock('Book[update]');
$mockBook->shouldReceive('update')->once();
$this->mock->shouldReceive('findOrFail')->once()->andReturn($mockBook);
If your database is a managed dependency and you use mock in your test it causes brittle tests.
Don't mock manage dependencies.
Manage dependencies: dependencies that you have full control over.

Model binding from input form

I'm working with Laravel and it seems in examples that they decide to implement validation inside controller, and I don't like it at all. What I want to ask is if there is some kind of bind method that can bind posted input fields to object that I created so that I can make sure my controllers are not messy.
I will try to explain what I want in code, I think it will be much clearer.
What I have
public function postRegister() {
$validation = Validator::make(Input::all(), array(
'email' => 'required|email',
'password' => 'required|min:6',
'name' => 'required|alpha',
'gender' => 'required|in:male,female'
));
if ($validation->fails()) {
Input::flashExcept('password');
return Redirect::to('register')->withErrors($validation)->withInput();
}
// Register user...
}
What I want to have
class UserRegisterDto {
public $email;
public $password;
public $name;
public $gender;
protected $errors;
public function isValid() {
// Validate it here, set errors if there are some
return $validator->isValid();
}
public function getErrors() {
return $this->errors;
}
}
public function postRegister() {
$user = Input::bind('UserRegisterDto'); // This is made-up function, I wonder if something like this exists
if ($user->isValid()) {
// Register user...
}
}
Ardent can help you to keep the controllers clean: https://github.com/laravelbook/ardent
"Ardent models use Laravel's built-in Validator class. Defining validation rules for a model is simple and is typically done in your model class as a static variable"

Categories