Reputation: 2008
Is it possible to mock or fake the output of a model method in Laravel 5.8?
For example, consider this model
class Website extends Model
{
public function checkDomainConfiguration($domain): bool
{
try {
$records = dns_get_record($domain, DNS_A);
} catch (ErrorException $e) {
return false;
}
if (isset($records[0]['ip']) && $records[0]['ip'] === $this->server->ipv4_address) {
return true;
}
return false;
}
}
for the purpose of the test, I need to tell phpunit that when this method fires (it is called in the controller), to return true, or false if I purposely want it to fail. In the test I factory up a website, and of course it will fail the php method dns_get_record
.
I've read the Laravel docs, and scoured google about mocking model methods, but can't seem to find anything, short of wrapping a big if
around the method that checks if i'm not in testing mode, and if i am just return true. <-- YUK.
UPDATE This is an example of how I call the method in the controller
class SomeController extends Controller
{
public function store(Website $website, Domain $domain)
{
if (! $website->checkDomainConfiguration($domain->domain)) {
return response([
'error' => 'error message'
], 500);
}
// continue on here if all good.
}
}
This is some code from the test
$website = factory(Website::class)->create();
$domain = factory(Domain::class)->create([
'website_id' => $website->id
]);
//Mock the website object
$websiteMock = \Mockery::mock(Website::class)->makePartial();
$websiteMock->shouldReceive('getAttribute')
->once()
->with('domain')
->andReturn($website->domain);
$websiteMock->shouldReceive('checkDomainConfiguration')
->with($domain->domain)
->andReturn(true);
app()->instance(Website::class, $websiteMock);
// tried end point like this
$response = $this->json(
'POST',
'api/my-end-point/websites/' . $website->domain . '/domain/' . $domain->id
);
//also tried like this
$response = $this->json(
'POST',
'api/my-end-point/websites/' . $websiteMock->domain . '/domain/' . $domain->id
);
The controller method accepts the website and the domain model bindings. if I dd(get_class($website))
at the top of the controller, it display the namespace for the actual model, not the mock.
Upvotes: 8
Views: 11243
Reputation: 2735
In your code you are injecting your model to the controller as a parameter, that means that Laravel's ioc will be asked for an instance of the Website
model and will provide it when executing the real code.
In your test, you can create a mock and then tell the ioc to return that mock when asked for an instance of Website
like:
$websiteMock = Mockery::mock(Website::class);
app()->instance(Website::class, $websitemock)
before executing your controller. Now when executing the controller on your test the mock will be the one that will receive the method calls and you can set it to expect them and fake the responses. Specifically on your code, your mock should expect and fake two calls like:
$websiteMock->shouldReceive('getAttribute')
->once()
->with('domain')
->andReturn('test.domain'):
$websiteMock->shouldReceive('checkDomainConfiguration')
->once()
->with('test.domain')
->andReturn(false):
Editing with the whole code (I've made up some things like the route or the controller name that are uninmportant, use your own).
Controller
<?php
namespace App\Http\Controllers;
use App\Domain;
use App\Website;
class StackOverflowController extends Controller
{
public function store(Website $website, Domain $domain)
{
if (! $website->checkDomainConfiguration($domain->domain)) {
return response([
'error' => 'error message'
], 500);
}
}
}
Integration test
public function testStackOverflow()
{
$website = Mockery::mock(Website::class);
app()->instance(Website::class, $website);
$domain = Mockery::mock(Domain::class);
app()->instance(Domain::class, $domain);
$domain->shouldReceive('getAttribute')
->once()
->with('domain')
->andReturn('some.domain');
$website->shouldReceive('checkDomainConfiguration')
->once()
->with('some.domain')
->andReturn(false);
$response = $this->json('POST', '/stack/websiteId/overflow/domainId');
$this->assertEquals('error message', json_decode($response->getContent())->error);
$this->assertEquals(500, $response->status());
}
Also I did not include the start of the integration test file, but double check you are including:
use App\Domain;
use App\Website;
Upvotes: 6