Unsure if I'm using mockery correctly - php

I'm grappling with mocking/Mockery for the first time and I'm unsure if the following test is actually touching my code, or is only testing the mock I've made? Also, I realize this code doesn't properly fit the repository pattern despite the fact it's name as such.. I'll work on that.
The class:
<?php namespace Acme\Cart\Repositories;
class EloquentCartRepository{
protected $model_name = 'CartModel';
protected $model;
public function __construct($model = null)
{
$this->model = is_null($model) ? new $this->model_name : $model;
}
public function create_visitor_cart($session_id,$type = 'main'){
return $this->create('visitor',$session_id,$type);
}
protected function create($user_type = null,$user_identifier = null,$type = 'main')
{
if(is_null($user_identifier)) throw new \Exception('Cannot create create cart, missing user identifier');
if(is_null($user_type)) throw new \Exception('Cannot create create cart, missing user type');
if($user_type == 'visitor')
{
$this->model->user_session_id = $user_identifier;
}
else
{
$this->model->user_id = $user_identifier;
}
$this->model->type = $type;
$this->model->save();
return $this->model;
}
}
And my test:
/** #test */
public function create_visitor_cart_calls_internal()
{
$model = m::mock('Models\CartModel');
$model->shouldReceive('user_session_id')->with('sess123');
$model->shouldReceive('type')->with('main');
$model->shouldReceive('save')->andReturn($model);
$repository = new EloquentCartRepository($model);
$created_model = $repository->create_visitor_cart('sess123','main');
$this->assertEquals('sess123',$created_model->user_session_id);
$this->assertEquals('main',$created_model->type);
}
Is this a proper way to test my class? Or is this incorrect use of Mockery/mocking?

Instead of testing what is returned, you should test that it is saved. That means, that ->save() is run. The expectation you've set on ->save() is $model->shouldReceive('save')->andReturn($model);. That doesn't make sense, since the code doesn't use the return value of ->save().
In programming, you usually deal with 2 types of method: Commands and Queries. Queries can get some value, do some logic and return a value. Commands can get some values, communicates with an extern source (e.g. a database) and return nothing. Queries should be stubbed (that means, they should not do any expectations on how much it is called, but only on what it returns) and commands should be mocked (that means, they should only contain expectatations on how much (and if) it is called).
The ->save() method is a command: It communicates with the database. So it should be mocked. To mock the object, use the ->once() method of Mockery. It sets an expectation that it should be called one time:
/** #test */
public function create_visitor_cart_calls_internal()
{
$model = m::mock('Models\CartModel');
$model->shouldReceive('save')->once();
$repository = new EloquentCartRepository($model);
$created_model = $repository->create_visitor_cart('sess123','main');
$this->assertEquals('sess123',$created_model->user_session_id);
$this->assertEquals('main',$created_model->type);
}
Despite its name, Mockery is a stubbing framework by default. It does not validate that a method is called unless you explicitely specify an expectation like ->once()
For more information, see the docs: https://github.com/padraic/mockery-docs/blob/master/reference/expectations.rst

Related

Replace all class instances with stub

I am testing a class, let's call it ClassUnderTest using another class, let's call it OtherClass. In my Test I do:
$OtherClassStub = $this->createStub(OtherClass::class);
$OtherClassStub->method(...)
->willReturn(...);
$ClassUnderTest->otherClass = $OtherClassStub;
That works. But when the $ClassUnderTest calls new OtherClass(), the original OtherClass class is created instead of the stub.
How can I achieve that every possible instance of OtherClass in the context of the test is replaced by the stub?
From your description I infer that in principle you have something like this:
class OtherClass {
protected function someMethod(): bool
{
// determine $x ...
return $x;
}
}
class ClassUnderTest {
public OtherClass $otherClass;
public function methodToBeTested(): bool
{
$otherClass = new OtherClass();
return $otherClass->someMethod();
}
}
class ClassUnderTestTest extends TestCase {
public function testMethodToBeTested(): void
{
$otherClassStub = $this->createStub(OtherClass::class);
$otherClassStub->method('someMethod')
->willReturn(true);
$classUnderTest = new ClassUnderTest();
$classUnderTest->otherClass = $otherClassStub;
$result = $classUnderTest->methodToBeTested();
$this->assertTrue($result);
}
}
Now the assertion in your test may hold or it may fail. Why? Because you are not calling the method you stubbed on the $otherClassStub. Instead you instantiate a new $otherClass object in the method you're testing (or somewhere down the line).
Either your ClassUnderTest should always use the OtherClass object from the ClassUndertTest::otherClass attribute (assuming that's why you put it there in the first place).
Or you could use some other form of dependency injection, e.g. by using a framework like Symfony or Laravel. (In the case of Symfony you can even use only the DependencyInjection Component, no idea if that's possible with Laravel, too.)
The simple answer to your actual question is: you cannot change the behaviour of the new keyword. Calling new on a class will always instantiate a new object based on exactly that class, unless the constructor of that class defines something else.
(You might want to get the concept of classes and objects straight, your code example as well as your question seem to indicate that you're not quite clear on that. Maybe reading up on that as well as on the concept of dependency injection will help you.)
Perhaps a solution to your problem is presented here:
How to Build a PHP Plugin Module System
This is one way to load classes as plugins and they can be called from each other. With modifying this system a bit, you can create as many "new OtherClass()" as you like from your code and still access everything from other classes. If you want multiple instances of a class, perhaps modify it into this direction:
function load ($module,$instance) {
if (isset($this->$module->$instance)) { return true; }
From above link:
<?php
class Core {
// (A) PROPERTIES
public $error = ""; // LAST ERROR MESSAGE
public $pdo = null; // DATABASE CONNECTION
public $stmt = null; // SQL STATEMENT
public $lastID = null; // LAST INSERT/UPDATE ID
// (B) LOAD SPECIFIED MODULE
// $module : module to load
function load ($module) {
// (B1) CHECK IF MODULE IS ALREADY LOADED
if (isset($this->$module)) { return true; }
// (B2) EXTEND MODULE ON CORE OBJECT
$file = PATH_LIB . "LIB-$module.php";
if (file_exists($file)) {
require $file;
$this->$module = new $module();
// EVIL POINTER - ALLOW OBJECTS TO ACCESS EACH OTHER
$this->$module->core =& $this;
$this->$module->error =& $this->error;
$this->$module->pdo =& $this->pdo;
$this->$module->stmt =& $this->stmt;
return true;
} else {
$this->error = "$file not found!";
return false;
}
}
}
ps. thank you for the mod, who made me work a bit more to keep this answer online. the answer is so much better now.

SUT parent checks dependencies with get_class

It started when I was performing null checks everywhere to make sure I have the necessary entities for my interactor. Fortunately, I came across this post which points towards not allowing the entities to be in an invalid state do the check in your constructor. Now my Interactors use protected static $request to state explicitly which entities they require, which are then passed in during instantiation. I chose static so the check could be done prior to creating an instance of the Interactor.
abstract class Interactor {
protected static $request = [];
protected $entities = [];
final public function __construct(Entity ...$entities) {
$this->setEntities(...$entities);
$this->checkEntities();
}
final private function setEntities(Entity ...$entities) {
foreach($entities as $entity) {
$this->setEntity($entity);
}
}
final private function setEntity(Entity $entity){
$className = get_class($entity);
if (!in_array($className, static::$request)){
throw new Exception("Not a requested entity");
}
$this->entities[$className] = $entity;
}
final private function checkEntities(){
if (count(static::$request) != count($this->entities))
throw new Exception("Entity mismatch");
foreach(static::$request as $index=>$name) {
if (!array_key_exists($name, $this->entities))
throw new Exception("Missing requested entity ($name)");
if (!is_a($this->entities[$name], $name))
throw new Exception("Not the specified entity");
}
}
final public static function getRequest(){
return array_values(static::$request);
}
}
Ok great, now I just do the check in a single location and I don't need to worry about performing null checks at the beginning of my functions. The problem with the way I am going about it now is that my Interactor is checking the class name against a static class name request array. Thus, when I DI the mocked entities during testing, my parent Interactor throws an exception saying it isn't in the pre approved list.
To demonstrate is the following simplified Chess example:
class Chess extends Interactor {
protected static $request = ['Piece','Engine','Board'];
}
Then we have our Entities:
abstract class Entity {}
class Piece extends Entity {}
class Engine extends Entity {}
class Board extends Entity {}
And finally our test:
class ChessTest extends TestCase {
function setUp(){
$this->piece = $this->getMockBuilder(Piece::class)->getMock();
$this->engine = $this->getMockBuilder(Engine::class)->getMock();
$this->board = $this->getMockBuilder(Board::class)->getMock();
$this->chess = new Chess($this->piece, $this->engine, $this->board);
}
function testCanSetup(){
$this->assertTrue(
is_a($this->chess, Chess::class)
);
}
}
Which throws Exception: Interactor receiving entity not requested (Mock_Piece_faaf8b14)
Of course Mock_Piece_faaf8b14 is not going to be in our static::$request array, so this is destined to throw an exception.
The workaround I have come up with so far is to include in Entity:
public function getClassName(){
return get_called_class();
}
Then in Interactor->setEntity($entity) instead of using get_class($entity) I would use $entity->getClassName() which then becomes trivial to mock.
I thought the way I had created the Interactor was inline with what the previously mentioned post was getting at, only take the entities in the constructor. However, it all feel apart when I injected mocked entities.
1) Is there a way to avoid getClassName() in my entities?
2) Is there something in the entities I can mock that gets called in get_class() instead?
Thank you for your help!
You are checking to see if the name of your class is one of the keys in your $request array. And it isn't. The keys in your array are numerical 0, 1, 2 so you are throwing the exception. I think that you want to use in_array instead.
Though at the same time, this still wouldn't pass with the mock because you are checking to see if the class name is in $request. So the name won't be there at all either and the exception will still be thrown.
If all that your Interactor class is doing is making sure that the correct objects are passed into the constructor why not just use PHP's native type hinting?
Your Chess class becomes:
class Chess {
public function __construct(Piece $piece, Engine $engine, Board $board) { }
}
PHP will make sure that the passed in objects are of the correct type and will allow you to mock them for testing.
You get the type checking that you are looking for without need to use getClassName() at all.

mocking out database queries laravel mockery

I am trying to wrap my head around Unit testing with PhpUnit / Mockery / Laravel.
It's not coming easy. I've been reading dozens of tutorials and still can't apply it in real life scenarios.
I will present a piece of code I would like to test. Can anyone please point me on how to test the method modifyBasedOnItemCode() of the class SoldProductModifier?
Few words of explanation first:
I want users to be able to type in the product code (item code) together with quantity, and I want the system to automatically update the product_id as well category_id properties for the SoldProduct model. For this purpose I created the class I now would like to test.
Please also see:
simplified diagram for my database (only tables related to my question)
Now relevant code:
Class to be tested
use App\Models\Product;
class SoldProductModifier
{
private $sold_product;
public function __construct(SoldProduct $sold_product)
{
$this->sold_product = $sold_product;
}
public function modifyBasedOnItemCode($item_code)
{
if (! isset($item_code) || $item_code == '')
{
$product = Product::findByItemCode($item_code);
if (isset($product) && $product != false)
{
$this->sold_product->category_id = $product->category->id;
$this->sold_product->product_id = $product->id;
}
}
return $this->sold_product;
}
}
Product Model
...
public static function findByItemCode($item_code) {
return self::where('item_code', $item_code)->first();
}
...
My controller referencing SUT
...
$sold_product = new SoldProduct($request->all());
$modifier = new SoldProductModifier($sold_product);
$sold_product = $modifier->modifyBasedOnItemCode($request->item_code);
$sold_product->save();
...
My test class
class SoldProductModifierTest extends TestCase {
public function setUp()
{
parent::setUp();
$this->soldProductMock = $this->mock('App\Models\SoldProduct');
$this->productMock = $this->mock('App\Models\Product');
}
public function tearDown()
{
Mockery::close();
}
public function testDoesNotModifyIfItemCodeEmpty()
{
$soldProductModifier = new SoldProductModifier($this->soldProductMock);
$modifiedSoldProduct = $soldProductModifier->modifyBasedOnItemCode('');
$this->assertEquals($this->soldProductMock, $modifiedSoldProduct);
}
public function testModifiesBasedOnItemCode()
{
// how do I test positive case scenario ?
...
I pasted my first test in case someone thinks it isn't the way it should be done and would be kind to suggest another way of approaching this.
But now to my question:
How do I mock out the call to database here: Product::findByItemCode($item_code) ?
Should I create a $product property in my SoldProductModifier and set it using a setter method created for this purpose, like:
public function setProduct(Product $product)
{
$this->product = $product;
}
and then add extra line in my controller:
...
$modifier = new SoldProductModifier($sold_product);
$modifier->setProduct(Product::findByItemCode($item_code)); // --- extra line
$sold_product = $modifier->modifyBasedOnItemCode(); // --- parameter removed
...
?
I try to keep my controllers as slim as possible, so wanted to avoid that?
So what is the best way to tackle this kind of situation?
Thank you
You should have Product injected via the constructor so Laravel can handle that for you.
use App\Models\Product;
class SoldProductModifier
{
private $sold_product;
protected $product;
public function __construct(SoldProduct $sold_product, Product $product)
{
$this->sold_product = $sold_product;
$this->product = $product;
}
}
Now you need to write one unit test for each "path" through the function.
// Build your mock object.
$mockProduct = Mockery::mock(new App\Models\Product);
// Have Laravel return the mocked object instead of the actual model.
$this->app->instance('App\Models\Product', $mockProduct);
// Tell your mocked instance what methods it should receive.
$mockProduct
->shouldReceive('findByItemCode')
->once()
->andReturn(false);
// Now you can instantiate your class and call the methods on it to be sure it's returning items and setting class properties correctly.
You should write this test multiple times and have your $mockProduct return different things until all lines of code have been covered. For example, you might want to do something like the following...
$product = new stdClass;
$product->id = 45;
$category = new stdClass;
$category-id = 60;
$product->category = $category;
$mockProduct
->shouldReceive('findByItemCode')
->once()
->andReturn($product);
Now after the function runs, you'd want to make sure sold_product->category_id is equal to 60 and sold_product->product_id is equal to 45. If they are private and you can't check them from the test, you might want to write a getter for those objects so you can more easily see their values from the test.
Edit
Regarding your comments, you'd use the following.
new SoldProductModifier($sold_product, new Product);
And then your function should look like...
public function modifyBasedOnItemCode($item_code)
{
if (! isset($item_code) || $item_code == '')
{
$product = $this->product->findByItemCode($item_code);
if (isset($product) && $product != false)
{
$this->sold_product->category_id = $product->category->id;
$this->sold_product->product_id = $product->id;
}
}
return $this->sold_product;
}
I see that it's a static function so you may want to handle that a bit differently. If it's static just for this reason, then you can simply not make it static. If other things are depending on it, you can likely make a new function which isn't static which calls the static function via self::findByItemCode($id)
The general rule of thumb here is unless it's a facade which has been setup in your config.php file, you should allow Laravel to handle injecting it for you. That way when you are testing, you can create mock objects and then let Laravel know about them via $this->app->instance() so it will inject those in place of the real ones.

Is it bad to mock the object being tested in a unit test?

Here is the class I am unit testing. Currently I am testing the doSomething function:
class FooClass {
public function doSomething( $user ) {
$conn = $this->getUniqueConnection( $user->id );
$conn->doSomethingDestructive();
}
private function getUniqueConnection( $id ) {
return new UniqueConnection( $id );
}
}
As you can see, the doSomething function gets a new instance of UniqueConnection (a class I am not testing here) based on a property of the argument it receives. The problem is that UniqueConnection:: doSomethingDestructive method is something I cannot call during tests due to its... destructiveness. So I would like to stub/mock the UniqueConnection rather than use a real one.
I don't see any way to inject my mocked UniqueConnection. I would make the UniqueConnection a constructor argument for FooClass but, as you can see, a new one gets created based on the parameter to the doSomething function and all the unique ids it may be called with are not known ahead of time.
My only option that I can see is to test a mock of FooClass instead of FooClass itself. Then I would replace the getUniqueConnection function with one that returns a mock/stub. This seems bad to test an mock, but I don't see any way to achieve what I am after otherwise. UniqueConnection is a third party vendor library and cannot be modified.
You could make a UniqueConnectionFactory, and pass an instance of that to FooClass. Then you have
private function getUniqueConnection( $id ) {
return $this->uniqueConnectionFactory->create( $id );
}
In general, this is one of the benefits of using a factory - you keep the new operator out of the class, which allows you to more easily vary the object being created.
Like Rambo Coder said, it's a matter of doing too much in your class. I wouldn't go as far as wanting to create a Factory, especially if you'll only ever create an instance of one specific class. The simplest solution would be to invert the responsibility of creating the UniqueConnection:
<?php
class FooClass {
public function doSomething( UniqueConnection $connection ) {
$connection->doSomethingDestructive( );
}
}
Pass a mock when you're testing, pass a new UniqueConnection( $user->id ) in the real code..
Until you can take the time to refactor the code to use a factory as rambo coder recommends, you can use a partial mock to return a non-destructive unique connection. When you find yourself in this position, it usually means the class under test has more than one responsibility.
function testSomething() {
$mockConn = $this->getMock('UniqueConnection');
$mockConn->expects($this->once())
->method('doSomethingDestructive')
->will(...);
$mockFoo = $this->getMock('FooClass', array('getUniqueConnection'));
$mockFoo->expects($this->once())
->method('getUniqueConnection')
->will($this->returnValue($mockConn));
$mockFoo->doSomething();
}
Creating classes in a way that it can support different modes of execution is very important in some cases. One of these cases is what you are asking for.
Create your classes to support various modes. For example
Class Connection {
private $mode;
public function setMode($mode) {
$this -> $mode = $mode;
}
}
Now, your doSomethingDestructive can act as per the execution mode.
public function doSomethingDestructive() {
if($this -> mode === "test") { //if we are in a test mode scenario
//Log something
// Or just do some logging and give a message
} else {
// do what it was suppose to do
}
}
Next time, when you are testing the class, you dont have to worry about that destructive function doing something destruction accidentally.
public function doSomething( $user ) {
$conn = $this->getUniqueConnection( $user->id );
$conn -> setMode("test"); //Now we are safe
$conn->doSomethingDestructive(); //But the Testing is still being Ran
}
In this case what you want is not a mock object, but a testing subclass. Break your $conn->doSomethingDestructive(); into a method, then subclass FooClass as TestFooClass and override the new method in the subclass. Then you can test using the subclass without getting the unwanted destructive behavior.
For example:
class FooClass {
public function doSomething( $user ) {
$conn = $this->getUniqueConnection( $user->id );
$this->connDoSomethingDestructive($conn);
}
protected function connDoSomethingDestructive($conn) {
$conn->doSomethingDestructive();
}
private function getUniqueConnection( $id ) {
return new UniqueConnection( $id );
}
}
class TestFooClass extends FooClass {
protected function connDoSomethingDestructive() {
}
private function getUniqueConnection( $id ) {
return new MockUniqueConnection( $id );
}
}

PropelORM, Symfony 2 and Unit testing

I'm used to the habit of writing like this:
$results = SomeModelQuery::create()->filterByFoo('bar')->find();
However this does not scale for unit testing because I can't inject a mock object, i.e. I can't affect what data is returned. I'd like to use fixture data, but I can't.
Nor does it seem great to inject an object:
class Foo
{
public __construct($someModelQuery)
{
$this->someModelQuery = $someMOdelQuery;
}
public function doSthing()
{
$results = $this->someModelQuery->filterByFoo('bar')->find();
}
}
DI feels horrible. I have tens of query objects to mock and throw. Setting through constructor is ugly and painful. Setting using method is wrong because it can be forgotten when calling. And it feels painful to always for every single lib and action to create these query objects manually.
How would I elegantly do DI with PropelORM query classes? I don't want to call a method like:
$oneQuery = OneQuery::create();
$anotherQuery = AnotherQuery::create();
// ... 10 more ...
$foo = new Foo($oneQuery, $anotherQuery, ...);
$foo->callSomeFunctionThatNeedsThose();
In my opinion (and Martin Folowers's) there is a step between calling everything statically and using Dependency Injection and it may be what you are looking for.
Where I can't do full DI (Zend Framework MVC for example) I will use a Service Locator. A Service Layer will be the place that all your classes go to get there dependencies from. Think of it as a one layer deep abstraction for your classes dependencies. There are many benefits to using a Service Locator but I will focus on testability in this case.
Let's get into some code, here is are model query class
class SomeModelQuery
{
public function __call($method, $params)
{
if ($method == 'find') {
return 'Real Data';
}
return $this;
}
}
All it does is return itself unless the method 'find' is called. Then is will return the hard-coded string "Real Data".
Now our service locator:
class ServiceLocator
{
protected static $instance;
protected $someModelQuery;
public static function resetInstance()
{
static::$instance = null;
}
public static function instance()
{
if (self::$instance === null) {
static::$instance = new static();
}
return static::$instance;
}
public function getSomeModelQuery()
{
if ($this->someModelQuery === null) {
$this->someModelQuery = new SomeModelQuery();
}
return $this->someModelQuery;
}
public function setSomeModelQuery($someModelQuery)
{
$this->someModelQuery = $someModelQuery;
}
}
This does two things. Provides a global scope method instance so you can always get at it. Along with allowing it to be reset. Then providing get and set methods for the model query object. With lazy loading if it has not already been set.
Now the code that does the real work:
class Foo
{
public function doSomething()
{
return ServiceLocator::instance()
->getSomeModelQuery()->filterByFoo('bar')->find();
}
}
Foo calls the service locator, it then gets an instance of the query object from it and does the call it needs to on that query object.
So now we need to write some unit tests for all of this. Here it is:
class FooTest extends PHPUnit_Framework_TestCase
{
protected function setUp()
{
ServiceLocator::resetInstance();
}
public function testNoMocking()
{
$foo = new Foo();
$this->assertEquals('Real Data', $foo->doSomething());
}
public function testWithMock()
{
// Create our mock with a random value
$rand = mt_rand();
$mock = $this->getMock('SomeModelQuery');
$mock->expects($this->any())
->method('__call')
->will($this->onConsecutiveCalls($mock, $rand));
// Place the mock in the service locator
ServiceLocator::instance()->setSomeModelQuery($mock);
// Do we get our random value back?
$foo = new Foo();
$this->assertEquals($rand, $foo->doSomething());
}
}
I've given an example where the real query code is called and where the query code is mocked.
So this gives you the ability to inject mocks with out needing to inject every dependency into the classes you want to unit test.
There are many ways to write the above code. Use it as a proof of concept and adapt it to your need.

Categories