I am writing a unittest for a Laravel project.
I would like to test the public method testMethod() in the following class.
class Foo extends Model
{
public static function staticMethod($arg)
{
return 'from staticMethod';
}
public function testMethod($arg)
{
$result = self::staticMethod($arg);
return $result;
}
}
Here is my test code:
1.
$mock = Mockery::mock('overload:'.Foo::class);
$mock->shouldReceive('staticMethod')
->once()
->with('test argument')
->andReturn('mocked return value');
$foo = new Foo();
$result = $foo->testMethod('test argument');
The above returns the result that: testMethod() does not exist on this mock object
.
2.
$mock = Mockery::mock(Foo::class);
$mock->shouldReceive('staticMethod')
->once()
->with('test argument')
->andReturn('mocked return value');
$this->app->instance(Foo::class, $mock);
$foo = new Foo();
$result = $foo->testMethod('test argument');
In this case, however, staticMethod()
was not mocked.
Edit:
Following @Charlie’s advice, I rewrote the test code as follows
$mock = Mockery::mock(Foo::class);
$mock->shouldReceive('staticMethod')
->once()
->andReturn('mocked return value');
$this->app->instance(Foo::class, $mock);
$mockedFoo = app(Foo::class);
$result = $mockedFoo->testMethod('test argument');
Here’s one question.
// return 'mocked return value'
$mockedFoo->staticMethod('test');
// return value of original method.
Foo::staticMethod('test');
In the code under test, I am calling it with Foo::staticMethod()
, not an instance.
I’m still trying this code, but is it possible to mock a statich method without overloading?
And this test returns;
MockeryExceptionBadMethodCallException: Received Mockery_2_App_Models_Foo::testMethod(), but no expectations were specified
This error means that the mock recognizes testMethod()
(I’m glad!).
But I want testMethod()
to work according to the original code, so I don’t want to set a return value for the mock.
So, I used makePartial()
.
$mock = Mockery::mock('overload:'.Foo::class)->makePartial();
Then, from now on, the staticMethod()
mock was not used when running the test, but the original staticMethod()
was used…
Does anyone have any ideas?
2
Answers
Two of your test contains same issue, you didn’t use Laravel app to get the instantiated class, when you use
new Foo()
, it actually instantiated a real Foo::class instead of the mocked class.An untested example:
You are nearly there, your issue with the new code is that you are not asking for partial mock, so whatever you have not mocked, will return an error (not defined). But you also need to still overload.
I will share a solution, a quick fix for your code:
But your original code needs to be changed, instead of
self::staticMethod($arg)
, it must bestatic::staticMethod($arg)
so whenMockery
overloads theFoo
class so it can simulate the static call,static
refers to the late binding (Mockery
is doing something likeMockeryClass1 extends Foo
, so if you haveself
, it will always refer toFoo
, so no mock, butstatic
will always refer toMockeryClass1
in this case, so the static mocked method will work).So, in code terms, Mockery does something like this:
Now, if you code is:
When using that
Mocker_class_fake_1
example class,self
resolves toFoo
ALWAYS, butstatic
resolves to the one extending and calling that method, in this caseMocker_class_fake_1::staticMethod($arg)
and then__callStatic
will be able to resolvestaticMethod
because you wroteshouldReceive('staticMethod')
, etc.But you need the 3 things:
overload:
.Foo::classmakePartial()
$this->app->instance(Foo::class, $mock);
andapp(Foo::class);
(orresolve(Foo::class);
, exact alias ofapp(...)
)My question would be why are you extending a Model and testing it, usually you do not mix this (of course you can, but that is a bad practice). So I would create a new domain class and separate the Model logic from the Domain logic, but I am not sure why you have it that way as you just put example code, not real code.
Hope this clarifies more!
More info about
__call
and__callStatic
.