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

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)

Related

How to construct an object that depends on the session to be loaded

Let's suppose we have a site that shows a random list of 20 movies. Logged in users, however, can select their favorite movies, so those movies will be shown instead. This list of movies is shown both in the home page and in some other pages.
To follow the DRY principle, we could encapsulate this logic in its own class, and then inject this class wherever it is necessary to show the list of movies. This class will also have other methods that will be used throughout the application. For example, there is also a method to get one random movie.
The class could look like this (please note this is a simplified example):
class MovieService
{
/** #var Collection $movies */
protected $movies;
public function __construct()
{
$this->movies = Auth::check() ? Auth::user()->favoriteMovies : $this->randomMovies();
}
public function getRandomMovies(): Collection
{
return $this->movies->random(20);
}
public function getOneRandom(): Movie {
return $this->movies->random();
}
protected function randomMovies() {
return Movie::inRandomOrder()->take(20)->get();
}
}
Note: Please note that this is an example and that some things could be improved.
As this class could be used multiple times in the same request, it is a good idea to make it a singleton in the IoC container, so that the queries that are run when instantiated are not run more than once.
However, now we encounter a problem. We need this class in a private method in a controller. We could directly call the app container like app() or App::make() but we would like to avoid facades and global helpers with custom dependencies.
class HomeController extends Controller
{
/** #var MovieService $movieService */
protected $movieService;
public function __construct(MovieService $movieService)
{
$this->movieService = $movieService;
}
public function index()
{
$movies = $this->getMovies();
return view('home', compact('movies'));
}
protected function getMovies()
{
// Let's imagine there's some extra logic here so that we would actually need this method.
return $this->movieService->getRandomMovies();
}
}
We have found a problem. A controller's constructor is run before the middleware pipeline, which means that there's no session and, hence, no user identification. Now Auth::check() in MovieService is always returning false, so the default movies will always be shown.
What would you do to fix this?
It's cleaner to not use the constructor of an object for logic, only for managing dependencies. Coincidentally this will also fix the issue you're having by moving the Auth::check() logic to your getter methods instead. Besides that you could also consider injecting the AuthManager instead of relying on the Auth facade, but that's just a sidenote.
class MovieService
{
/** #var AuthManager $auth */
protected $auth;
protected $movies;
public function __construct(Illuminate\Auth\AuthManager $auth)
{
$this->auth = $auth;
}
public function getRandomMovies(): Collection
{
return $this->getMoviesForCurrentUser()->random(20);
}
public function getOneRandom(): Movie {
return $this->getMoviesForCurrentUser()->random();
}
protected function randomMovies() {
if ($this->movies === null) {
$this->movies = Movie::inRandomOrder()->take(20)->get();
}
return $this->movies;
}
protected function getMoviesForCurrentUser() {
if ($this->auth->check()) {
return $this->auth->user->favoriteMovies;
}
return $this->randomMovies();
}
}

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 ..

Mocking class parameter that returns a mock

I am new to unit testing and trying to test a controller method in Laravel 5.1 and Mockery.
I am trying to test a registerEmail method I wrote, below:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Response;
use Mailchimp;
use Validator;
/**
* Class ApiController
* #package App\Http\Controllers
*/
class ApiController extends Controller
{
protected $mailchimpListId = null;
protected $mailchimp = null;
public function __construct(Mailchimp $mailchimp)
{
$this->mailchimp = $mailchimp;
$this->mailchimpListId = env('MAILCHIMP_LIST_ID');
}
/**
* #param Request $request
* #return \Illuminate\Http\JsonResponse
*/
public function registerEmail(Request $request)
{
$this->validate($request, [
'email' => 'required|email',
]);
$email = $request->get('email');
try {
$subscribed = $this->mailchimp->lists->subscribe($this->mailchimpListId, [ 'email' => $email ]);
//var_dump($subscribed);
} catch (\Mailchimp_List_AlreadySubscribed $e) {
return Response::json([ 'mailchimpListAlreadySubscribed' => $e->getMessage() ], 422);
} catch (\Mailchimp_Error $e) {
return Response::json([ 'mailchimpError' => $e->getMessage() ], 422);
}
return Response::json([ 'success' => true ]);
}
}
I am attempting to mock the Mailchimp object to work in this situation.
So far, my test looks as follows:
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class HomeRouteTest extends TestCase
{
use WithoutMiddleware;
public function testMailchimpReturnsDuplicate() {
$listMock = Mockery::mock('Mailchimp_Lists')
->shouldReceive('subscribe')
->once()
->andThrow(\Mailchimp_List_AlreadySubscribed::class);
$mailchimp = Mockery::mock('Mailchimp')->lists = $listMock;
$this->post('/api/register-email', ['email'=>'duplicate#email.com'])->assertJson(
'{"mailchimpListAlreadySubscribed": "duplicate#email.com is already subscribed to the list."}'
);
}
}
I have phpUnit returning a failed test.
HomeRouteTest::testMailchimpReturnsDuplicate
Mockery\Exception\InvalidCountException: Method subscribe() from Mockery_0_Mailchimp_Lists should be called exactly 1 times but called 0 times.
Also, if I assert the status code is 422, phpUnit reports it is receiving a status code 200.
It works fine when I test it manually, but I imagine I am overlooking something fairly easy.
I managed to solve it myself. I eventually moved the subscribe into a seperate Job class, and was able to test that be redefining the Mailchimp class in the test file.
class Mailchimp {
public $lists;
public function __construct($lists) {
$this->lists = $lists;
}
}
class Mailchimp_List_AlreadySubscribed extends Exception {}
And one test
public function testSubscribeToMailchimp() {
// create job
$subscriber = factory(App\Models\Subscriber::class)->create();
$job = new App\Jobs\SubscribeToList($subscriber);
// set up Mailchimp mock
$lists = Mockery::mock()
->shouldReceive('subscribe')
->once()
->andReturn(true)
->getMock();
$mailchimp = new Mailchimp($lists);
// handle job
$job->handle($mailchimp);
// subscriber should be marked subscribed
$this->assertTrue($subscriber->subscribed);
}
Mockery will expect the class being passed in to the controller be a mock object as you can see here in their docs:
class Temperature
{
public function __construct($service)
{
$this->_service = $service;
}
}
Unit Test
$service = m::mock('service');
$service->shouldReceive('readTemp')->times(3)->andReturn(10, 12, 14);
$temperature = new Temperature($service);
In laravel IoC it autoloads the classes and injects them, but since its not autoloading Mailchimp_Lists class it won't be a mock object. Mailchimp is requiring the class atop it's main class require_once 'Mailchimp/Lists.php';
Then Mailchimp is then loading the class automatically in the constructor
$this->lists = new Mailchimp_Lists($this);
I don't think you'll be able to mock that class very easily out of the box. Since there isn't away to pass in the mock object to Mailchimp class and have it replace the instance of the real Mailchimp_Lists
I see you are trying to overwrite the lists member variable with a new Mock before you call the controller. Are you certain that the lists object is being replaced with you mocked one? Try seeing what the classes are in the controller when it gets loaded and see if it is in fact getting overridden.

PHPSpec and Laravel - how to handle double method not found issues

I appear to be having issues with my spec tests when it comes to stubs that are calling other methods.
I've been following Laracasts 'hexagonal' approach for my controller to ensure it is only responsible for the HTTP layer.
Controller
<?php
use Apes\Utilities\Connect;
use \OAuth;
class FacebookConnectController extends \BaseController {
/**
* #var $connect
*/
protected $connect;
/**
* Instantiates $connect
*
* #param $connect
*/
function __construct()
{
$this->connect = new Connect($this, OAuth::consumer('Facebook'));
}
/**
* Login user with facebook
*
* #return void
*/
public function initialise() {
// TODO: Actually probably not needed as we'll control
// whether this controller is called via a filter or similar
if(Auth::user()) return Redirect::to('/');
return $this->connect->loginOrCreate(Input::all());
}
/**
* User authenticated, return to main game view
* #return Response
*/
public function facebookConnectSucceeds()
{
return Redirect::to('/');
}
}
So when the route is initialised I construct a new Connect instance and I pass an instance of $this class to my Connect class (to act as a listener) and call the loginOrCreate method.
Apes\Utilities\Connect
<?php
namespace Apes\Utilities;
use Apes\Creators\Account;
use Illuminate\Database\Eloquent\Model;
use \User;
use \Auth;
use \Carbon\Carbon as Carbon;
class Connect
{
/**
* #var $facebookConnect
*/
protected $facebookConnect;
/**
* #var $account
*/
protected $account;
/**
* #var $facebookAuthorizationUri
*/
// protected $facebookAuthorizationUri;
/**
* #var $listener
*/
protected $listener;
public function __construct($listener, $facebookConnect)
{
$this->listener = $listener;
$this->facebookConnect = $facebookConnect;
$this->account = new Account();
}
public function loginOrCreate($input)
{
// Not the focus of this test
if(!isset($input['code'])){
return $this->handleOtherRequests($input);
}
// Trying to stub this method is my main issue
$facebookUserData = $this->getFacebookUserData($input['code']);
$user = User::where('email', '=', $facebookUserData->email)->first();
if(!$user){
// Not the focus of this test
$user = $this->createAccount($facebookUserData);
}
Auth::login($user, true);
// I want to test that this method is called
return $this->listener->facebookConnectSucceeds();
}
public function getFacebookUserData($code)
{
// I can't seem to stub this method because it's making another method call
$token = $this->facebookConnect->requestAccessToken($code);
return (object) json_decode($this->facebookConnect->request( '/me' ), true);
}
// Various other methods not relevant to this question
I've tried to trim this down to focus on the methods under test and my understanding thus far as to what is going wrong.
Connect Spec
<?php
namespace spec\Apes\Utilities;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use \Illuminate\Routing\Controllers\Controller;
use \OAuth;
use \Apes\Creators\Account;
class ConnectSpec extends ObjectBehavior
{
function let(\FacebookConnectController $listener, \OAuth $facebookConnect, \Apes\Creators\Account $account)
{
$this->beConstructedWith($listener, $facebookConnect, $account);
}
function it_should_login_the_user($listener)
{
$input = ['code' => 'afacebooktoken'];
$returnCurrentUser = (object) [
'email' => 'existinguser#domain.tld',
];
$this->getFacebookUserData($input)->willReturn($returnCurrentUser);
$listener->facebookConnectSucceeds()->shouldBeCalled();
$this->loginOrCreate($input);
}
So here's the spec that I'm having issues with. First I pretend that I've got a facebook token already. Then, where things are failing, is that I need to fudge that the getFacebookUserData method will return a sample user that exists in my users table.
However when I run the test I get:
Apes/Utilities/Connect
37 ! it should login the user
method `Double\Artdarek\OAuth\Facade\OAuth\P13::requestAccessToken()` not found.
I had hoped that 'willReturn' would just ignore whatever was happening in the getFacebookUserData method as I'm testing that separately, but it seems not.
Any recommendations on what I should be doing?
Do I need to pull all of the OAuth class methods into their own class or something? It seems strange to me that I might need to do that considering OAuth is already its own class. Is there some way to stub the method in getFacebookUserData?
Update 1
So I tried stubbing the method that's being called inside getFacebookUserData and my updated spec looks like this:
function it_should_login_the_user($listener, $facebookConnect)
{
$returnCurrentUser = (object) [
'email' => 'existinguser#domain.tld',
];
$input = ['code' => 'afacebooktoken'];
// Try stubbing any methods that are called in getFacebookUserData
$facebookConnect->requestAccessToken($input)->willReturn('alongstring');
$facebookConnect->request($input)->willReturn($returnCurrentUser);
$this->getFacebookUserData($input)->willReturn($returnCurrentUser);
$listener->facebookConnectSucceeds()->shouldBeCalled();
$this->loginOrCreate($input);
}
The spec still fails but the error has changed:
Apes/Utilities/Connect
37 ! it should login the user
method `Double\Artdarek\OAuth\Facade\OAuth\P13::requestAccessToken()` is not defined.
Interestingly if I place these new stubs after the $this->getFacebookUserData stub then the error is 'not found' instead of 'not defined'. Clearly I don't fully understand the inner workings at hand :D
Not everything, called methods in your dependencies have to be mocked, because they will in fact be called while testing your classes:
...
$facebookConnect->requestAccessToken($input)->willReturn(<whatever it should return>);
$this->getFacebookUserData($input)->willReturn($returnCurrentUser);
...
If you don't mock them, phpspec will raise a not found.
I'm not familiar with the classes involved but that error implies there is not method Oauth:: requestAccessToken().
Prophecy will not let you stub non-existent methods.

ZF2 : Call a service from an external class?

In my Zend Framework 2 project, I have an external lib and I want to save my information in the base with the model.
....
....
....
EDITED MESSAGE :
I explain again my need: In my controllers, I make insertions and deletions in the database and I want to log all actions in a "t_log" table . To do it, I have thought to create an external class.
My question is: How I can call my models method from my external class ?
namespace Mynamespace;
use Firewall\Model\Logs;
use Firewall\Model\LogsTable;
class StockLog
{
public function addLog()
{
$log = $this->getServiceLocator()->get('Firewall\Model\LogTable');
$log->save('user added');
die('OK');
}
}
My model :
namespace Firewall\Model;
use Zend\Db\TableGateway\TableGateway;
use Zend\Db\Sql\Select;
class UserGroupTable
{
protected $tableGateway;
public function __construct(TableGateway $tableGateway)
{
$this->tableGateway = $tableGateway;
}
public function save()
{
// How I Can call this method from the StockLog method ?
}
}
Thanks you !
getServiceLocator is a function of \Zend\Mvc\Controller\AbstractActionController so it is supposed to be used in your controllers.
I dont know what your StockLog class is, but it is not extending any other class, so i guess it has not that function and your error is one step before, in the call to getSErviceLocator that is not defined, so its not returning an object.
Probably you can inject the service locator with something like
class StockLog
{
private $serviceLocator= null;
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
$this->serviceLocator = $serviceLocator;
}
public function add()
{
# Do you know how I can call the service ??
$User = $this->serviceLocator->get('Firewall\Model\UserTable');
}
}
and then, when you create your StockLog object, in your controller, you inject the servicelocator
public class yourController extends AbstractActionController {
public function yourAction(){
$mStockLog = new StockLog ();
$mStockLog->setServiceLocator($this->getServiceLocator());
/...
}
}
Also, if you only need the 'Firewall\Model\UserTable' service, you should inject just that, instead of the serviceLocator.
At any rate you should minimice the knowledge of your model classes about the rest of the system, hving always in mind the dependency inversion principle, to get a better decoupling
UPDATE
inject the log table
namespace Mynamespace;
use Firewall\Model\Logs; use Firewall\Model\LogsTable;
class StockLog {
private $logTable= null;
public function setLogTable($logTable)
{
$this->logTable= $logTable;
}
public function addLog()
{
$this->logTable->save('user added');
die('OK');
}
}
and then, when you create your StockLog (in your controller, or wherever you do it, before you use it) you inject the logtable object
$mStockLog = new StockLog ();
$mStockLog->setLogTable($this->getServiceLocator()->get('Firewall\Model\LogTable'));
Of course, Im suposing that you configured correctly your Firewall\Model\LogTable class to be retrieved by means of the service manager, in getServiceConfig() in your Module.php
public function getServiceConfig() {
return array (
'factories' => array (
'Firewall\Model\LogTable' => function ($sm) {
$logTable = //create it as you use to
return $logTable;
}
)
}

Categories