Save variable during unit testing Laravel - php

Variable $this->id not visible in another function testExemple
If I pass this variable to a normal function that does not start on a “test” and not to a testing function, everything will work.
Can I fix this somehow?
class LoginTest extends TestCase
{
protected $id;
public function testLogin()
{
$response = $this->json('post', 'auth/login',
['email' => 'admin#mail.com', 'password' => '12345678'])
->assertJsonStructure(['data' => ['id', 'name', 'email']]);
$response->assertStatus(201);
$userData = $response->getContent();
$userData = json_decode($userData, true);
$this->id = $userData['data']['id'];
}
public function testExemple()
{
echo($this->id);
}
}

Each test runs independently as far as I know, if you want to pass data from one test to another you can use the #depends doc comment like below:
class LoginTest extends TestCase
{
public function testLogin()
{
$response = $this->json('post', 'auth/login',
['email' => 'admin#mail.com', 'password' => '12345678'])
->assertJsonStructure(['data' => ['id', 'name', 'email']]);
$response->assertStatus(201);
$userData = $response->getContent();
$userData = json_decode($userData, true);
return $userData['data']['id']; //Return this for the dependent tests
}
/**
* #depends testLogin
*/
public function testExample($id)
{
echo($id);
}
}
However the problem you might encounter is that while the $id has a value the user is not actually logged in during this test because everything else (e.g. session) will be wiped clean.
To ensure the user is logged in then you will need to mock user login like below:
public function testExample()
{
$this->actingAs(User::where('email', 'admin#mail.com')->first()); //User now logged in
echo(\Auth::id());
}
This ensures the user is logged in and also decouples tests.

It works like this because unit tests should be indepedent. A variable set by one test should never be accessible for the next.
If your issue is that you need to test things that requires you to be logged in, a good solution is creating a new class that extends TestCase and implementing helper functions such as loginUser() (which could return a User instance).
You should then have your tests extend this new class instead of directly extending TestCase.
Every time you run a test that requires you to log in, you can just write $this->loginUser() and go ahead with your real test.
If all tests in a class requires you to log in, you can even add a setUp() function that will run right before any test is executed (remember to also call parrent::setUp():
protected function setUp() {
parent::setUp();
$this->loginUser();
}

Lets suppose you want to pass $number from test1 to test2 :
class Test extends TestCase
{
public function test1()
{
$number = 1;
// store the variable, only available during testsuite execution,
// it does not necessarily have to exist in your .env file .
putenv('MY_NUMBER=' . $number);
//success
$this->assertEquals(getenv('MY_NUMBER'), $number);
}
public function test2()
{
//hurray ! variable retrieved
$this->assertEquals(1, getenv('MY_NUMBER'));
// after tearing down , MY_NUMBER is cleared
}
}
The solution may not be the best, but at least it works. And hey, we are doing testing, not writting production code, so who cares?

Related

Laravel: Testing Policies with Spatie Permissions Package

I'm looking to find a way to test Policy methods in isolation. The policies work as intended when Feature testing (e.g. via http) but I am having a hard time trying to write tests for these when I just want to test the logic of the policy itself.
My code:
class ItemPolicy
{
...
public function delete(User $user, Item $item)
{
return $this->can('delete_item') && !$item->isDeletable();
}
...
}
class ItemPolicyTest extends TestCase
{
use RefreshDatabase;
private $user;
public function setUp() : void
{
parent::setUp();
$this->user = User::factory()->create();
Permission::create(['name' => 'delete_item', 'guard_name' => 'web']);
$role = Role::create(['name' => 'admin', 'guard_name' => 'web']);
$role->givePermissionTo('delete_item');
$this->user->assignRole(['admin']);
$this->user = $this->user->fresh(); //just to make sure
$this->app->make(\Spatie\Permission\PermissionRegistrar::class)->registerPermissions();
}
/** #test */
public function it_will_not_allow_deletion()
{
$item = Item::factory()->cannotBeDeleted()->create();
$this->be($this->user, 'web');
$this->assertFalse(auth()->user()->can('delete', $item));
}
}
Setting a breakpoint in my Policy doesn't get triggered so I assume that Spatie has taken this over somewhere. So ultimately, I'm looking for a way to recreate this so I can test these in isolation. Any help appreciated!

PHP: Mockery Mock variable $user = Auth::user()

So, I am trying to mock a service method.
In my service file:
/**
* Return all Api Keys for current user.
*
* #return Collection
*/
public function getApiKeys(): Collection
{
$user = Auth::user();
return ApiKey::where('org_id', $user->organizationId)->get();
}
How do I mock this?
<?php
namespace App\Services;
use PHPUnit\Framework\TestCase;
use Mockery as m;
class ApiKeysServiceTest extends TestCase
{
public function setUp()
{
parent::setUp();
/* Mock Dependencies */
}
public function tearDown()
{
m::close();
}
public function testGetApiKeys()
{
/* How to test? $user = Auth::user() */
$apiKeysService->getApiKeys();
}
}
In my TestCase class I have:
public function loginWithFakeUser()
{
$user = new GenericUser([
'id' => 1,
'organizationId' => '1234'
]);
$this->be($user);
}
What I want to do is test this method. Maybe this involves restructuring my code so that $user = Auth::user() is not called in the method. If this is the case, any thoughts as to where it should go?
Thanks for your feedback.
In your testGetApiKeys method you're not setting up the world. Make a mock user (using a factory as suggested in the comments factory('App\User')->create()), then setup an apiKey again using the factory, then call the method and assert it's what you've setup. An example with your code
public function loginWithFakeUser()
{
$user = factory('App\User')->create();
$this->be($user);
}
public function testApiSomething()
{
$this->loginWithFakeUser();
// do something to invoke the api...
// assert results
}
A good blueprint for the test structure is:
Given we have something (setup all the needed components)
If the user does some action (visits a page or whatever)
Then ensure the result of the action is what you expect (for example the status is 200)

Laravel - Initialize class only for specific routes

In my app I've got a group of routes which need some bootstraping before dispatching.
To illustrate the situation:
There is a special routes group with prefix 'app'. All of this routes have also some params:
site.dev/app/index?age=11&else=af3fs4ta21
Without these params user shouldn't be allowed to access route. I've got it done by creating a simple route middleware.
if (!$request->exists('age') || !$request->exists('else')) {
return redirect('/');
}
Next step is to initialize a class which takes route parameters as a construct arguments. Then param "else" is being used as a argument to db calls. I need to access this class in every route from /app route group.
In order to achive that I tried setting up a serviceprovider:
public function register()
{
$this->app->singleton(Dual::class, function ($app) {
return new Dual($this->app->request->all());
});
}
Then I created a special controller extending BaseController and passing Dual class to its constructor.
class DualController extends Controller
{
public function __construct(Request $request, Dual $dual)
{
$this->middleware(\App\Http\Middleware\DualMiddleware::class);
$this->dual = $dual;
}
}
And then every single controller is extending DualController and accessing Dual class by $this->dual->method().
It is working if route params are in their place and there is already a row in a database.
The problem
This middleware is executed AFTER ServiceProvider & DualController are initializing class Dual. So, middleware is not really working. If route params are not present it is going to fail.
Moreover, in case that there is no required row in database for some reason, Dual class will not be initialized (as it depends on calls to db) and whole app will crash saying that I am trying to perform operations on null.
Desired behaviour
First check route for params presence.
Second, check if there is row in db with key from route.
Third - try to initialize Dual class and pass it to all controllers used by route group /app.
If any of the steps fail -> display proper message.
Part of dual class:
class Dual
{
protected $client = null;
public $config = [];
public function __construct($config)
{
$this->config = $config;
$this->bootstrap();
}
public function getEli()
{
$eli = Eli::where(['else' => $this->config['else']])->first();
return $eli;
}
public function instantiateClient()
{
$client = Client::factory(Client::ADAPTER_OAUTH, [
'entrypoint' => $this->getEli()->eli_url,
'client_id' => '111',
'client_secret' => '111',
]);
$client->setAccessToken($this->getEli()->accessToken()->first()->access_token);
return $client;
}
public function getClient()
{
if ($this->client === null)
{
throw new \Exception('Client is NOT instantiated');
}
return $this->client;
}
public function bootstrap()
{
$this->client = $this->instantiateClient();
}
You can do this in middleware:
$isElseExists = Model::where('else', request('else'))->first();
if (request('age') && request('else') && $isElseExists) {
return $next($request);
} else {
return back()->with('error', 'You are not allowed');
}
If everything is fine, controller method will be executed. Then you'll be able to inject Dual class without any additional logic.
If something is wrong, a user will be redirected to previous URI with error message flashed into session.

In Laravel, how to give another implementation to the service container when testing?

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

PHP OOP-based login system

Lets say I am building an OOP-based user authentication system, and I would like to incorporate the following principles: Direct Injection, Inheritance, Encapsulation, Polymorphism and the Single Responsibility Principle.
My background in programming is has always relied on procedural programming, and thus, am finding it difficult to really put these practices into correct use.
Assume I have these classes:
class Config
{
public function set($key, $value);
public function get($key, $default = null);
}
class User
{
public function __construct(PDO $dbh, $id = null);
public function setProfile(Profile $profile);
}
class Auth
{
public function __construct(Config $config);
public function login($username, $password, $keepLoggedIn = true);
public function isLoggedIn();
public function getLoggedInUser();
public function logout();
public function register(array $data);
}
class Session
{
public function start($sessionName = null);
public function write($key, $value);
public function read($key, $default = null);
}
class Profile
{
public function setAddress(Address $address);
public function setName($name);
public function setDOB(DateTime $date);
public function getAge();
}
class Validator
{
public function validate($input);
}
I have intentionally left off the function bodies to keep things simple.
To the best of my knowledge, I believe I'm using the principles correctly. However, I am still unclear as to how you would connect classes like: the Validator to the User model, the User model to the Auth and the Session to the Auth class. All of which depend on each other.
You are on the right track. The way these classes connect to each other is called extending. I tend to go towards an MVC setup, meaning Model, View, Controller.
Your logic goes into the controller, all your DB queries and concrete back end methods go in the model. The controller receives requests and returns responses. It's the middleman. It talks to the back end after a request has been made to it, and feeds the front in via response.
So you have a core controller (keep it bare minimal), then each class you make extends the core controller. So your controller is where you tie all this together.
<?php
//your main core controller, where you load all these things you need avilable, so long as this class is extended
class CoreController {
public $auth
public $session;
public $view;
function construct__ ()
{
$this->auth = instantiateAuthClassHere();
$this->session = instantiateSessionClassHere();
$this->view = instantiateViewClassHere();
}
public function anotherHelperForSomething(){
//helper stuff for this method
}
}
//index, page, or content controller, depending on how many you need, i.e. if you want a controller for each page, thats fine, e.g indexController, etc..
//this is the middle man, has logic, receives requst, returns response to view.
class Controller extends CoreController {
public function index (){
$userModel = new userModel();
//do something with this
$session = $this->session;
$content = 'some html';
$userInfo = $userModel->getUsers();
$view = $this->view->render( array(
'content' => $content,
'userInfo' => $userInfo,
));
return $view;
}
}
//Core LIbraries
class Validator {
//your validator stuff
}
//Core LIbraries
class Session {
//your validator stuff
}
//Core LIbraries
class Auth {
//your validator stuff
}
class CoreModel{
public $validator;
function __construct(){
$this->validator = instantiateValidatorClassHere();
}
}
//a user model class (back end). you want a model class for each db table pretty much.
class UserModel extends CoreModel {
// if you need the validator anywhere inside this class, its globally available here inside any class that extends the CoreModel, e.g. $this->validator->methodName()
public function getUsers (){
$sql = 'SELECT * from users';
$result = $db->get($sql);
return $result;
}
}
Notice, on the Controller, this is a generic name for something like indexController, or anything custom. Also, I have the word extends there. It inherits all the objects from the parent that it extends. Inside it, now they will be available via $this->. See my example where I get $this->session.
Try to avoid constructs - you probably don't need them anywhere except for the core, and under special circumstances, which you might then need to check for yourself before you do even that. I dont use constructs much anymore. It can be a bit clunky and unmanageable.

Categories