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);
}
Related
I have some doubt what to inject. Given this code:
class A
{
public function getSomething()
{
return 'something';
}
}
class TestMe
{
/**
* #var A
*/
private $a;
public function __construct($a)
{
$this->a = $a;
}
public function greetings()
{
return 'Hello, '.$this->a->getSomething();
}
}
my test A:
function testA()
{
$a = new class() {
public function getSomething()
{
return 'aAnonimus';
}
};
$sut = new TestMe($a);
$this->assertEquals($sut->greetings(), 'Hello, aAnonimus');
}
testB, same but with mock:
function testA()
{
$a = $this->createMock(A::class);
$a->method('getSomething')->willReturn('bMockery');
$sut = new TestMe($a);
$this->assertEquals($sut->greetings(), 'Hello, bMockery');
}
in the first test I simply inject a plain object.
But the second its more Phpunit's way: using mocked objects.
Question is, for long period which one wins? I find the first more conviement, and for the 2nd test, you have to know the class name of dependency (otherwise you cant create a mock)
In the longterm, it's better the second way because it's better to have type hint in the constructor which will not allow you to provide a simple object.
Also when we are talking about UnitTests you should test a certain class without depending on 3rd party libraries or other services logic. So the best way is to use mocks for all of the services which are part of the tested class
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!
Let's say I have a small class that handles connections to MySQL databases:
class myDatabaseHandler{
private $_databases;
public function addDatabase($name, $dsn, $username, $password){
$this->_databases[$name] = array('dsn' => $dsn, 'username' => $username, 'password' => $password);
return true;
}
public function getDatabase($name){
$connectionInfo = $this->_databases[$name];
$database = new Database($connectionInfo['dsn'], $connectionInfo['username'], $connectionInfo['password']);
$database->doSomeSetup();
$database->attachMoreThings();
return $database;
}
}
I want to unit test these 2 methods:
class myDatabaseHandlerTest extends \PHPUnit_Framework_TestCase
{
public function testAddDatabase(){
}
public function testGetDatabase(){
}
}
How can this I test those 2 methods? If I addDatabase(), at most it would return a Boolean telling me the operation succeeded. Since it writes to a private property, I cannot confirm that the correct data is indeed written to it.
I feel that using getDatabase() to get a Database object back and testing against it is not exactly ideal, because I would need to expose dsn, username and password just for the sake of testing. In addition, it is possible that the Database object might modify those values to a format it uses, so I need to store the original values just for testing.
What is the best way to approach this problem?
Testing certainly gets tricky when you try to construct and use an object in the same place. In this case, you are both constructing a Database and calling methods on it in your getDatabase($name) method. That makes it pretty much impossible to mock, for instance, and to get decent coverage your tests would need to test the functionality provided by the Database class to make sure the system was behaving as expected.
A better way might be using a proper factory as a dependency.
interface iDatabaseFactory
{
public function buildDatabase($dsn, $username, $password);
}
Then, you could mock both the database factory and the database instance itself to verify that it is both constructed correctly and initialized correctly:
class MockDatabaseFactory implements iDatabaseFactory
{
public $databaseParams = array();
public $databaseToReturn = NULL;
public function buildDatabase($dsn, $username, $password)
{
$this->databaseParams['dsn'] = $dsn;
$this->databaseParams['username'] = $username;
$this->databaseParams['password'] = $password;
return $this->databaseToReturn;
}
}
class myDatabaseHandlerTest extends PHPUnit_Framework_TestCase
{
public function testAddAndGetDatabaseUsesCorrectDbParameters(){
$mockDatabaseFactory = new MockDatabaseFactory();
$dbHandler = new myDatabaseHandler($mockDatabaseFactory);
// implement MockDatabase according to your interface
$mockDatabase = new MockDatabase();
$mockDatabaseFactory->databaseToReturn = $mockDatabase;
$dbHandler.addDatabase("some name", "some dsn",
"some username", "pa$$w0rd");
$builtDatabase = $dbHandler.getDatabase("some name");
$this->assertEquals($mockDatabase, $builtDatabase);
$dbParams = $mockDatabaseFactory->databaseParams;
$this->assertEquals("some dsn", $dbParams['dsn']);
$this->assertEquals("some username", $dbParams['username']);
$this->assertEquals("pa$$w0rd", $dbParams['password']);
}
public function testAddAndGetDatabaseInitializesDb(){
$mockDatabaseFactory = new MockDatabaseFactory();
$dbHandler = new myDatabaseHandler($mockDatabaseFactory);
$mockDatabase = new MockDatabase();
$mockDatabaseFactory.setDatabaseToBuild($mockDatabase);
$dbHandler.addDatabase("name", "dsn", "user", "pass");
$builtDatabase = $dbHandler.getDatabase("some name");
$this->assertTrue($mockDatabase->doSomeSetupWasCalled);
$this->assertTrue($mockDatabase->attachMoreThingsWasCalled);
}
}