In unit tests, this doesn't work:
class SomeClassCest
{
public function tryToTest(UnitTester $I)
{
$mock = Mockery::mock(MyClass::class);
$mock->shouldReceive('action')->once()->andReturn(true);
$I->haveInstance(MyClass::class, $mock);
$classToTest = app(SomeClass::class);
// this method calls the MyClass action method
$classToTest->run();
// some asserts here
}
}
The mock doesn't take effect, the $mock->action is never called, the real implementation is called instead. How to mock classes on unit tests with Codeception and the Laravel 5 module?
Usually, in unit tests, the methods amOnRoute, amOnPage are not called, so the Laravel5->doRequest() will not apply bindings, so, in order to mock a class, in the Unit helper from Codeception suite:
class Unit extends \Codeception\Module
{
/**
* #param $realClass
* #param $mock
*/
public function mockClass($realClass, $mock)
{
$this->getModule('Laravel5')->getApplication()->instance($realClass, $mock);
}
}
In the unit test:
class SomeClassCest
{
public function tryToTest(UnitTester $I)
{
$mock = Mockery::mock(MyClass::class);
$mock->shouldReceive('action')->once()->andReturn(true);
$I->mockClass(MyClass::class, $mock);
$classToTest = app(SomeClass::class);
// this method calls the MyClass action method
$classToTest->run();
// the mocked classs is used insetad of the real one
// do some asserts here
}
}
Related
I have class Name Validator and it has a method forVote.
This is my code.
public function test_should_set_default()
{
$this->mock = \Mockery::mock(Validator::class);
$this->mock->shouldReceive('forVote')
->andReturnTrue();
$this->app->instance(Validator::class,$this->mock);
$factory = new Factory();
$this->assertTrue($factory->setDefault());
}
So Factory calls Processor which calls Validator. Now I want mock validator to run. But it calls the real method.
What am I doing wrong?
https://laravel.com/docs/5.6/container#introduction
since the repository is injected, we are able to easily swap it out
with another implementation. We are also able to easily "mock", or
create a dummy implementation of the UserRepository when testing our
application.
My guess is you are perhaps currently instantiating your dependencies like so:
$processor = new Processor() and $validator = Validator::make(...);
So, in order to have your mocked class be used, you should use Dependency injection which just means your classes should inject your dependencies via the __construct method.
Your Factory class should be like:
class Factory {
$processor;
public function __construct(Processor $processor)
{
$this->processor = $processor;
}
public function setDefault()
{
$this->processor->callingValidator();
}
}
and your Processor to be like:
class Processor {
$validator;
/**
* The Validator will resolve to your mocked class.
*
*/
public function __construct(Validator $validator)
{
$this->validator = $validator;
}
public function callingValidator()
{
$this->validator->make();
}
}
If we have the following class that uses the Builder design pattern:
class CourseListingBuilder extends Component
{
/**
* #var yii\db\Query
*/
private $query;
private $data = [];
public function init()
{
parent::init();
$this->query = new yii\db\Query();
}
/**
* Return a new instance of the builder
* #return CourseListingBuilder
*/
public static function create()
{
return new CourseListingBuilder();
}
public function selectColumns(array $columns)
{
// #TODO validate and format $columns
$this->query->select($columns);
return $this;
}
public function applyFilters($filters = [])
{
// #TODO validate and parse filters
$this->query->andWhere($filters);
return $this;
}
public function build()
{
// Make the actual DB query
$this->data = $this->query->all();
}
public function getData()
{
return $this->data;
}
}
We use it like this:
$data = CourseListingBuilder::create()
->selectColumns(['id', 'name'])
->applyFilters(['active'=>1])
->build()
->getData();
In time, these classes grow quite large because we have multiple developers working on them. Developers extend the selectColumns() or applyFilters() and occasionally, the code breaks. We've recently started introducing Unit Tests in the company and we would like to make classes that follow the above pattern - unit testable, preferrably through a small refactoring.
How do you properly unit test the above builder design pattern class, given its internal dependency to yii\db\Query (which comes from the Yii2 framework by the way, but this is not relevant to the example). What's relevant to the example is that we DO NOT want to test the internal behaviors of yii\db\Query. It comes from the framework. We know that it's working. In other words - we want to "mock" it as much as possible, while effectively testing the actual methods inside our Builder class and how they will affect the outcome.
The second thing to point out is that we know how to write unit tests in general. This question is not about "how to write unit tests" in general, but "how to write unit tests for a builder class that has an internal dependency to a third party DAO class".
Are we doing something wrong?
Such code is not unit testable but instead goes in the integration tests territory?
The simplest way would be to replace yii\db\Query with some simple class and test its state after method call.
class MockQuery extends \yii\db\Query {
public $select;
public $selectOption;
public function select($columns, $option = null) {
$this->select = $columns;
$this->selectOption = $option;
}
// ...
}
class CourseListingBuilder extends \yii\base\Component {
private $query;
public function selectColumns(array $columns) {
// #TODO validate and format $columns
$this->query->select($columns);
return $this;
}
// ...
}
And test:
public function testQuery() {
$builder = new CourseListingBuilder();
// use reflection to access private property
$reflection = new \ReflectionObject($builder);
$property = $reflection->getProperty('query');
$property->setAccessible(true);
$property->setValue($builder, new MockQuery());
$builder->selectColumns(['id', 'name']);
$query = $property->getValue($builder);
$this->assertSame(['id', 'name'], $query->select);
$this->assertNull($query->selectOption);
}
I am trying to set up the simplest of tests in my controller but, as with most things Laravel, there are no decent tutorials to demonstrate the simple stuff.
I can run a simple test (in a file called UserControllerTest) like this:
public function testIndex()
{
$this->call('GET', 'users');
$this->assertViewHas('users');
}
This calls the /users route and passes in an array users.
I want to do the same with Mockery but how?
If I try this:
public function testIndex()
{
$this->mock->shouldReceive('users')->once();
$this->call('GET', 'users');
}
I get an error that "Static method Mockery_0_users::all does not exist on this mock object.
Why not? I am mocking User which extends Ardent and in turn extends Eloquent. Why does ::all not exist for the mock?
BTW, these are the set-up functions for Mockery:
public function setUp()
{
parent::setUp();
$this->mock = $this->mock('User');
}
public function mock($class)
{
$mock = Mockery::mock($class);
$this->app->instance($class, $mock);
return $mock;
}
You can't directly mock an Eloquent class. Eloquent is not a Facade and your User model neither. There is a bit of magic in Laravel but you can't do things like that.
If you want to mock your User class, you have to inject it in the controller constructor. The repository pattern is a good approach if you want to do that. There is a lot of articles about this pattern and Laravel on Google.
Here some pieces of code to show you how it could look like :
class UserController extends BaseController {
public function __construct(UserRepositoryInterface $users)
{
$this->users = $users;
}
public function index()
{
$users = $this->users->all();
return View::make('user.index', compact('users'));
}
}
class UserControllerTest extends TestCase
{
public function testIndex()
{
$repository = m::mock('UserRepositoryInterface');
$repository->shouldReceive('all')->andReturn(new Collection(array(new User, new User)));
App::instance('UserRepositoryInterface', $repository);
$this->call('GET', 'users');
}
}
If it seems to be too much structuration for your project you can just call a real database in your tests and don't mock your model classes... In a classic project, it just works fine.
This function is part of a project called apiato.io you can use it to mock any class in Laravel, even facade, basically anything that can be resolved with the IoC, which is almost all classes if you are using proper dependency injection:
/**
* Mocking helper
*
* #param $class
*
* #return \Mockery\MockInterface
*/
public function mock($class)
{
$mock = Mockery::mock($class);
App::instance($class, $mock);
return $mock;
}
i got a question when i was unit testing my application. I Have a method that require a dependency but only that method need it so i thought to don't inject it by construct but initialize it with App::make() of the IoC container Class. But now how can i unit test that?
Let's say a short example for understand how you unit testing this function of example
class Example {
public function methodToTest()
{
$dependency = App::make('Dependency');
return $dependency->method('toTest');
}
}
Test
public function test_MethodToTest() {
$dependency = m::mock('Dependency');
$dependency->shouldReceive('method')->once()->with('toTest')->andReturn(true);
$class = new Example();
$this->assertTrue($class->methodToTest('toTest')); // does not work
}
You're almost there. Create an anonymous mock with the expectations that you need and then register that mock as the instance for Dependency and you should be good to go.
That would look something like this
public function test_MethodToTest() {
$dependency = m::mock();
$dependency->shouldReceive('method')->once()->with('toTest')->andReturn(true);
App::instance('Dependancy', $dependancy);
$class = new Example();
$this->assertTrue($class->methodToTest()); // should work
}
I would prefer to inject the dependency in Example classes constructor.
class Example{
/** #var Dependency */
private $dependency;
public function __construct(Dependency $dependency){
$this->dependency = $dependency;
}
public function methodToTest(){
return $this->dependency->method('toTest');
}
}
class Test{
public function test_MethodToTest(){
$mock = Mockery::mock(Dependency::class);
$mock->shouldReceive('method')->once()->with('toTest')->andReturn(true);
$class = new Example($mock);
$this->assertTrue($class->methodToTest());
}
}
In your controller, libraries you can then use IoC like this
$example = App::make(Example::class);
I am unit testing my Laravel 4 Controller by mocking my repository that the controller expects. The problem is with the "store" function. This is the function that is called by Laravel when I do a POST to the given controller. The function gets called, but it is expected itemData as an input but I don't know how to provide that. Here is what I've tried:
ItemEntryController
class ItemEntryController extends BaseController
{
protected $itemRepo;
public function __construct(ItemEntryRepositoryInterface $itemRepo)
{
$this->itemRepo = $itemRepo;
}
public function store()
{
if(Input::has('itemData'))
{
$data = Input::get('itemData');
return $this->itemRepo->createAndSave($data);
}
}
}
Test class
<?php
use \Mockery as m;
class ItemEntryRouteAndControllerTest extends TestCase {
protected $testItemToStore = '{"test":12345}';
public function setUp()
{
parent::setUp();
$this->mock = $this->mock('Storage\ItemEntry\ItemEntryRepositoryInterface');
}
public function mock($class)
{
$mock = m::mock($class);
$this->app->instance($class, $mock);
return $mock;
}
public function testItemStore()
{
Input::replace($input = ['itemData' => $this->testItemToStore]);
$this->mock
->shouldReceive('createAndSave')
->once()
->with($input);
$this->call('POST', 'api/v1/tools/itementry/items');
}
Well, you got a few options.
Integration testing
You may want to follow the unit testing docs, which actually has a call() method which allows you set all of this. This bootstraps the app and will use your databases, etc.
This is more of an integration test than unit test, as it uses your actual class implementations.
This may actually be preferable, as Unit testing controllers may not actually make much sense (it doesn't do much, in theory, but call other already-unit-tested classes). But this gets into unit testing vs integration testing vs acceptance testing and all the nuances that apply therein. (Read up!)
Unit Testing
If you're actually looking to unit test, then you need to make your controller unit-testable (ha!). This (likely) means injecting all dependencies:
class ItemEntryController extends BaseController
{
protected $itemRepo;
// Not pictured here is actually making sure an instance of
// Request is passed to this controller (via Service Provider or
// IoC binding)
public function __construct(ItemEntryRepositoryInterface $itemRepo, Request $input)
{
$this->itemRepo = $itemRepo;
$this->request = $input;
}
public function store()
{
if($this->input->has('itemData'))
{
// Get() is actually a static method so we use
// the Request's way of getting the $_GET/$_POST variables
// see note below!
$data = $this->input->input('itemData');
return $this->itemRepo->createAndSave($data);
}
}
}
Sidenote: The Input facade is actually an instance of Request objet with an extra static method get()!
So now that we aren't using Input any longer, and are injecting the Request object, we can unit test this class by mocking the Request object.
Hope that helps!