I'm having difficulty with the spy and mock in Laravel 7 test when I test for MyCustomClass.
I have tried both mock before running $this->get and spy after $this->get. Both with the same error message (*below).
When running debug in the controller the $myCustomClass is still the MyCustomClass and not the mocked object.
MyCustomClass
class MyCustomClass
{
public function execute()
{
return 'hello';
}
MyController
class MyController
{
public function show()
{
$myCustomClass = new MyCustomClass();
$data = $myCustomClass->execute();
return $data;
}
private $mySpy;
public function testAMethod()
{
$spy = $this->spy(MyCustomClass::class);
$response = $this->get('/my/path');
$spy->shouldHaveReceived('execute');
$response->assertStatus(200);
}
Error
Method execute(<Any Arguments>) from Mockery_2_App_MyCustomClass should be called
at least 1 times but called 0 times.
The problem is that you are instantiating MyCustomClass yourself with the new keyword.
In order for Laravel to be able to swap out the real class with the spy you have to use the service container.
Something like this:
class MyController
{
public function show(MyCustomClass $myCustomClass)
{
return $myCustomClass->execute();
}
}
Provider
namespace App\Providers;
class ElasticSearchProvider extends ServiceProvider
{
public function register()
{
$hosts = [
'elasticsearch'
];
$instance = Elasticsearch\ClientBuilder::create()
->setHosts($hosts)
->build();
$this->app->instance('App\ESClient', $instance);
}
}
Actual Class
namespace App\Mappings;
class Categories implements Mappable
{
public $es;
public function __construct(App\ESClient $es)
{
$this->es = $es;
}
public function setMapping()
{
}
public function getMapping()
{
}
}
Test Case
use App\Mappings\Categories;
class CategoriesTest extends TestCase
{
private $instance;
public function testShouldReturnElasticSearchInstance()
{
$categories = new Categories();
dd($categories->es);
}
}
1) CategoriesTest::testShouldReturnElasticSearchInstance
ErrorException: Argument 1 passed to
App\Mappings\Categories::__construct() must be an instance of
App\Mappings\App\ESClient, none given,
So in here DI is not working, or i have register something wrong how can we test it ?
Thanks
You can use Mockery for test case. So in this example your test case would looks like:
use App\ESClient;
use App\Mappings\Categories;
use Mockery as m;
class CategoriesTest extends TestCase
{
private $instance;
public function testShouldReturnElasticSearchInstance()
{
$esClient = m::mock(ESClient::class);
$categories = m::mock(new Categories($esClient));
dd($categories->es);
}
}
This will provides you a mocked class of App\ESClient that you will inject into partialy mocked class of App\Mappings\Categories. When you learn to use mockery in your unit test you will find that this is the best opion for test purpose - because mock object (and Laravel Facades) can be override while testing and can catch every method call of the object in test (with shouldReceive method of the mocked class).
I created the following example test case:
<?php
abstract class Model
{
//...
public static function factory($data)
{
$className = get_called_class();
$obj = new $className($data);
return $obj;
}
}
class User extends Model
{
}
class ExampleController
{
protected $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function create()
{
return $this->user->factory(array('name' => 'Jim'));
}
}
class ExampleTest extends PHPUnit_Framework_TestCase
{
public function testSomething()
{
$user = new User(array('name' => 'Jim'));
$modelStub = $this->getMockBuilder('User')
->disableOriginalConstructor()
->getMock();
$modelStub
->method('factory')
->with(array('name' => 'Jim'))
->willReturn($user);
$example = new ExampleController($modelStub);
$this->assertEquals($user, $example->create());
}
}
However I get the following error:
1) ExampleTest::testSomething
PHPUnit_Framework_MockObject_BadMethodCallException:
I seems to work fine when I remove the static keyword, then my test passes. But I want my Model class to also allow, in other cases, the option to call certain methods without having to instantiate first:
// when instantiation is required
$userModel = new User();
$user = $userModel->factory(array('name' => 'Jim'));
// called statically, no initial instantiation required
$user = User::factory(array('name' => 'Jim'));
I came across this blog which states that methods declared statically, yet called dynamically, is ok. However, methods declared dynamically, yet called statically, will throw a STRICT error - http://www.lornajane.net/posts/2010/declaring-static-methods-in-php
I have also used Laravel's Eloquent before and it appears that both method calls are possible there:
// Eloquent example without initial instantiation is possible too
$user = User::find(1);
Anyway, regardless of whether my code works, I want to be able to mock these methods declared statically. It seems upon reading that PHPUnit just doesn't handle static methods well (I read there was a staticExpects method, but now deprecated as of PHPUnit 3.8). So I'm about to embark on trying some alternative testing frameworks (Codeception and AspectMock, PHPSpec, mockery) as I haven't much experience with others. Would really appreciate some pointers for this issue or advice on the matter as it would really help too in unit testing legacy applications at our company, thanks
The answer is AspectMock.
This library give answers for this questions:
How would you fake the time() function to produce the same result for each test call? Is there any way to stub a static method of a class? Can you redefine a class method at runtime?
You can not call $this->user->factory until you have factory method defined statically. You should change it to User::factory. You can mock such static methods with Moka:
class ExampleController
{
private $_userClass;
public function __construct($userClass = 'User')
{
$this->_userClass = $userClass;
}
public function create()
{
return $this->_userClass::factory(array('name' => 'Jim'));
}
}
class ExampleControllerTest extends \PHPUnit_Framework_TestCase
{
public function testCreateReturnsUser()
{
$userClass = Moka::stubClass(null, ['::factory' => 'USER']);
$controller = new ExampleController($userClass);
$this->assertEquals('USER', $controller->create());
$this->assertEquals(
[[['name' => 'Jib']],
$userClass::$moka->report('::factory')
);
}
}
Game.php
<?php
class Game
{
public $db;
public function __construct()
{
$this->db = new DB;
}
public function result()
{
return $this->db->data();
}
}
DB.php
<?php
class DB
{
public function data()
{
return false;
}
}
GameTest.php
<?php
use Mockery as m;
class GameTest extends PHPUnit_Framework_TestCase
{
public function testResult()
{
$game = m::mock(new Game);
$game->shouldReceive('data')
->once()
->andReturn(true);
$expected = $game->result();
$this->assertTrue($expected);
}
public function tearDown()
{
m::close();
}
}
This is my solution but totally not work, I guess if I want to get setting from __construct I need to mock a new class, I got message Failed asserting that false is true. which mean the mock thing is not work, how to deal with it?
You can't do it like that, best solution would be to use dependency injection for $db.
That way you can mock only DB like this...
$dbMock = m::mock('DB');
$dbMock->shouldReceive('data')
->once()
->andReturn(true);
Or you can keep you constructor like this (without DI), but you will have to mock that constructor also.
You can use Mockery by creating an "instance mock" for the DB class like this:
$dbMock = Mockery::mock('overload:MyNamespace\DB');
This will "intercept" when a new instance of the DB class is created and the $dbMock will be used instead. When the $dbMock is created you just need to add an expectation declaration for the given method:
$dbMock->shouldReceive('data')
->once()
->andReturn(true);
I am looking for the best way to go about testing the following static method (specifically using a Doctrine Model):
class Model_User extends Doctrine_Record
{
public static function create($userData)
{
$newUser = new self();
$newUser->fromArray($userData);
$newUser->save();
}
}
Ideally, I would use a mock object to ensure that fromArray (with the supplied user data) and save were called, but that's not possible as the method is static.
Any suggestions?
Sebastian Bergmann, the author of PHPUnit, recently had a blog post about Stubbing and Mocking Static Methods. With PHPUnit 3.5 and PHP 5.3 as well as consistent use of late static binding, you can do
$class::staticExpects($this->any())
->method('helper')
->will($this->returnValue('bar'));
Update: staticExpects is deprecated as of PHPUnit 3.8 and will be removed completely with later versions.
There is now the AspectMock library to help with this:
https://github.com/Codeception/AspectMock
$this->assertEquals('users', UserModel::tableName());
$userModel = test::double('UserModel', ['tableName' => 'my_users']);
$this->assertEquals('my_users', UserModel::tableName());
$userModel->verifyInvoked('tableName');
I would make a new class in the unit test namespace that extends the Model_User and test that. Here's an example:
Original class:
class Model_User extends Doctrine_Record
{
public static function create($userData)
{
$newUser = new self();
$newUser->fromArray($userData);
$newUser->save();
}
}
Mock Class to call in unit test(s):
use \Model_User
class Mock_Model_User extends Model_User
{
/** \PHPUnit\Framework\TestCase */
public static $test;
// This class inherits all the original classes functions.
// However, you can override the methods and use the $test property
// to perform some assertions.
}
In your unit test:
use Module_User;
use PHPUnit\Framework\TestCase;
class Model_UserTest extends TestCase
{
function testCanInitialize()
{
$userDataFixture = []; // Made an assumption user data would be an array.
$sut = new Mock_Model_User::create($userDataFixture); // calls the parent ::create method, so the real thing.
$sut::test = $this; // This is just here to show possibilities.
$this->assertInstanceOf(Model_User::class, $sut);
}
}
Found the working solution, would to share it despite the topic is old.
class_alias can substitute classes which are not autoloaded yet (works only if you use autoloading, not include/require files directly).
For example, our code:
class MyClass
{
public function someAction() {
StaticHelper::staticAction();
}
}
Our test:
class MyClassTest
{
public function __construct() {
// works only if StaticHelper is not autoloaded yet!
class_alias(StaticHelperMock::class, StaticHelper::class);
}
public function test_some_action() {
$sut = new MyClass();
$myClass->someAction();
}
}
Our mock:
class StaticHelperMock
{
public static function staticAction() {
// here implement the mock logic, e.g return some pre-defined value, etc
}
}
This simple solution doesn't need any special libs or extensions.
Mockery's Alias functionality can be used to mock public static methods
http://docs.mockery.io/en/latest/reference/creating_test_doubles.html#creating-test-doubles-aliasing
Another possible approach is with the Moka library:
$modelClass = Moka::mockClass('Model_User', [
'fromArray' => null,
'save' => null
]);
$modelClass::create('DATA');
$this->assertEquals(['DATA'], $modelClass::$moka->report('fromArray')[0]);
$this->assertEquals(1, sizeof($modelClass::$moka->report('save')));
One more approach:
class Experiment
{
public static function getVariant($userId, $experimentName)
{
$experiment = self::loadExperimentJson($experimentName):
return $userId % 10 > 5; // some sort of bucketing
}
protected static function loadExperimentJson($experimentName)
{
// ... do something
}
}
In my ExperimentTest.php
class ExperimentTest extends \Experiment
{
public static function loadExperimentJson($experimentName)
{
return "{
"name": "TestExperiment",
"variants": ["a", "b"],
... etc
}"
}
}
And then I would use it like so:
public function test_Experiment_getVariantForExperiment()
{
$variant = ExperimentTest::getVariant(123, 'blah');
$this->assertEquals($variant, 'a');
$variant = ExperimentTest::getVariant(124, 'blah');
$this->assertEquals($variant, 'b');
}
Testing static methods is generally considered as a bit hard (as you probably already noticed), especially before PHP 5.3.
Could you not modify your code to not use static a method ? I don't really see why you're using a static method here, in fact ; this could probably be re-written to some non-static code, could it not ?
For instance, could something like this not do the trick :
class Model_User extends Doctrine_Record
{
public function saveFromArray($userData)
{
$this->fromArray($userData);
$this->save();
}
}
Not sure what you'll be testing ; but, at least, no static method anymore...