Laravel 5.1 Mockery expectation ignored while testing controller - php

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();
}

Related

Save variable during unit testing Laravel

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?

Setting attributes on models while testing

I am trying to test a controller and am mocking the models. All seems to go well until the view is loaded, it's unable to retrieve properties on that view that are supposed to be loaded via relationships.
I've tried setting those properties using andSet() on the mocked object, but then that gives me an error getAttribute() does not exist on this mocked object..
Here's my controller method.
public function __construct(ApplicationRepositoryInterface $application)
{
$this->beforeFilter('consumer_application');
$this->application = $application;
}
public function edit($application_id)
{
$application = $this->application->find($application_id);
$is_consumer = Auth::user()->isAdmin() ? 'false' : 'true';
return View::make('application.edit')
->with('application', $application)
->with('is_consumer', $is_consumer)
->with('consumer', $application->consumer);
}
And my test...
public function setUp()
{
parent::setUp();
$this->mock = Mockery::mock($this->app->make('ApplicationRepositoryInterface'));
}
public function testEdit()
{
$this->app->instance('ApplicationRepositoryInterface', $this->mock);
$this->mock
->shouldReceive('find')
->once()
->andReturn(Mockery::mock('Application'))
->andSet('consumer', Mockery::mock('Consumer'));
Auth::shouldReceive('user')
->once()
->andReturn(Mockery::mock(array('isAdmin' => 'true')));
$application_id = Application::first()->id;
$this->call('GET', 'consumer/application/'.$application_id.'/edit');
$this->assertResponseOk();
$this->assertViewHas('application');
$this->assertViewHas('is_consumer');
$this->assertViewHas('consumer');
}
The furthest I have gotten is removing the the andSet() portion which takes care of the getAttribute() does not exist on this mock object but then it tells me consumer is undefined when the view is loaded and still fails.
You should change:
Auth::shouldReceive('user')
->once()
->andReturn(Mockery::mock(array('isAdmin' => 'true')));
To this:
Auth::shouldReceive('user->isAdmin')
->once()
->andReturn('true');

Laravel mock with Mockery Eloquent models

I'm developing a PHP (5.4.25) application with laravel(4.2) framework. I'd like test my UserController with Mockery, so I've fit my UserController in this way:
class UsersController extends \BaseController {
protected $user;
public function __construct(User $user) {
$this->user = $user;
$this->beforeFilter('csrf', array('on'=>'post'));
}
public function store() {
$validator = Validator::make(Input::all(), User::$rules);
if ( $validator->passes() ) {
$this->user->username = Input::get('username');
$this->user->password = Hash::make(Input::get('password'));
$this->user->first_name = Input::get('first_name');
$this->user->last_name = Input::get('last_name');
$this->user->email = Input::get('email');
$this->user->save();
return true;
} else {
return false;
}
}
I want mock Eloquent User model so i develop my UsersControllerTest so:
class UsersControllerTest extends TestCase {
private $mock;
public function __construct() {}
public function setUp() {
parent::setUp();
$this->createApplication();
}
public function tearDown() {
parent::tearDown();
Mockery::close();
}
public function testStore() {
$this->mock = Mockery::mock('Eloquent','User[save]');
$this->mock
->shouldReceive('save')
->once()
->andReturn('true');
$this->app->instance('User', $this->mock);
$data['username'] = 'qwerety';
$data['first_name'] = 'asd';
$data['last_name'] = 'asd123';
$data['email'] = 'test#gmail.com';
$data['password'] = 'password';
$data['password_confirmation'] = 'password';
$response = $this->call('POST', 'users', $data);
var_dump($response->getContent());
}
}
When I run my test it returns me this error:
Mockery\Exception\InvalidCountException : Method save() from Mockery_0_User should be called
exactly 1 times but called 0 times.
Why? What's wrong?
EDIT: I found the problem - If I don't use mock object all works fine and the controller create a new user in the DB, but when I use mock the Input:all() method returns empty array.
--
Thanks
i had this same issue when i started testing.....
the thing there is that, in your userscontroller store method you are actually saving the user to the database and base on your code it might work just fine but you are surprise that it is actually failing the test.
Now think of it this way, you mock the user, and you told your test that when ever i call User model, please give me the mock version of my user, like you did in your code line below
$this->app->instance('User', $this->mock);
Now the problem is that you need to also call the mock version of save() method from the through the mock version of User like so:
$this->mock->save();
Consider the following Example:
public function testStore()
{
$input = ['name', 'Canaan'];
$this->mock
->shouldReceive('create')
->once()->with($input);
$this->app->instance('User', $this->mock);
$this->mock->create($input);
$this->call('POST', 'users', $input);
}
i hope it helps you.
The way you are testing this, in the controller constructor is passed an instance of the real Eloquent model, not the mock. If you want to test the facades (as clearly stated in the documentation) you should call the shouldReceive() method directly on the facade to have it mocked.
But, since you are redefining the $this->user variable in the store() method of the controller, it will not be called, unless you remove the hardcoded instantiation and use the injected $user.
Edit: i overlooke the $this->app->instance('User', $this->mock); line. So, the problem may be due the fact that in the store method you are getting a new class instance directly, and not via the Laravel IoC container. instead of $this->user = new User; in your store method, you should get it via App::make('User');
The problem was in $this->createApplication();.
I have commented that line and Input::all() works fine with all input parameters!

PhpSpec return always null on mocks

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.

Extend/override Eloquent create method - Cannot make static method non static

I'm overriding the create() Eloquent method, but when I try to call it I get Cannot make static method Illuminate\\Database\\Eloquent\\Model::create() non static in class MyModel.
I call the create() method like this:
$f = new MyModel();
$f->create([
'post_type_id' => 1,
'to_user_id' => Input::get('toUser'),
'from_user_id' => 10,
'message' => Input::get('message')
]);
And in the MyModel class I have this:
public function create($data) {
if (!Namespace\Auth::isAuthed())
throw new Exception("You can not create a post as a guest.");
parent::create($data);
}
Why doesn't this work? What should I change to make it work?
As the error says: The method Illuminate\Database\Eloquent\Model::create() is static and cannot be overridden as non-static.
So implement it as
class MyModel extends Model
{
public static function create($data)
{
// ....
}
}
and call it by MyModel::create([...]);
You may also rethink if the auth-check-logic is really part of the Model or better moving it to the Controller or Routing part.
UPDATE
This approach does not work from version 5.4.* onwards, instead follow this answer.
public static function create(array $attributes = [])
{
$model = static::query()->create($attributes);
// ...
return $model;
}
Probably because you are overriding it and in the parent class it is defined as static.
Try adding the word static in your function definition:
public static function create($data)
{
if (!Namespace\Auth::isAuthed())
throw new Exception("You can not create a post as a guest.");
return parent::create($data);
}
Of course you will also need to invoke it in a static manner:
$f = MyModel::create([
'post_type_id' => 1,
'to_user_id' => Input::get('toUser'),
'from_user_id' => 10,
'message' => Input::get('message')
]);

Categories