-
Notifications
You must be signed in to change notification settings - Fork 11.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Test using $this->mock(Request::class)
always returning empty GET request
#37789
Comments
@driesvints yeah you'd probably want some sort of abstracted Laravel mock interface instead of wiring the Mockery one to the Laravel container |
Well, I'm a little confused it ever behaved any other way. You mocked the request? Why would the framework not use the mock you specified? Is there a reason you are mocking the request at all instead of just making a request using |
Can you please share your entire test as well as the controller? |
Using When I was investigating this, I was simply getting back an empty GET request. I am guessing this gets setup somewhere, and the singleton then just re-uses whenever you make another |
@trovster can you please provide the code to reproduce this? |
The issue can be reproduced like so: $this->app->instance('request', Request::create('/example'));
$this->mock(Request::class, function (MockInterface $mock) {
});
// works
$this->assertInstanceOf(MockInterface::class, resolve(Request::class));
// fails
$this->assertInstanceOf(MockInterface::class, resolve('request')); This version works in both cases: $this->app->instance('request', Request::create('/example'));
$this->mock('request', function (MockInterface $mock) {
});
$this->assertInstanceOf(MockInterface::class, resolve(Request::class)); // works
$this->assertInstanceOf(MockInterface::class, resolve('request')); The |
I'm unsure if this is the correct approach for solving this. However, it does resolve this particular issue - assuming that my reproduction (above) is accurate. |
Will be fixed here: #37810 |
@Nacoma that is actually an entirely different bug you have solved. I will write a test case. |
In contrary v8.48.1 → v8.48.2 breaks my test...
public function testInitializationForMailables(): void
{
$this->mock(Mailer::class)->shouldReceive('send');
// [7.x] Multiple Mailers Per App
// https://github.com/laravel/framework/pull/31073
if (interface_exists(MailerFactory::class)) {
$this->mock(MailerFactory::class)
->shouldReceive('mailer')
->andReturn($this->app->make(Mailer::class));
}
DB::connection();
$this->assertFalse($this->getRecordsModifiedViaReflection());
Mail::send(new GeneralMailable());
// ...
} |
@mpyw you're misusing that it seems. You need to provide a callback with the assertions so they're bound to the container: https://laravel.com/docs/8.x/mocking#mocking-objects |
@driesvints In short, the following fix doesn't work. $this->mock(Mailer::class, function (MockInterface $mock) {
$mock->shouldReceive('send');
});
// [7.x] Multiple Mailers Per App
// https://github.com/laravel/framework/pull/31073
if (interface_exists(MailerFactory::class)) {
$this->mock(MailerFactory::class, function (MockInterface $mock) {
$mock
->shouldReceive('mailer')
->andReturn($this->app->make(Mailer::class));
});
} As you may know,
And protected function singletonInstance($abstract, $instance)
{
$abstract = $this->app->getAlias($abstract);
$this->app->singleton($abstract, function () use ($instance) {
return $instance;
});
// Returns second argument. So this returns MockInterface.
return $instance;
}
protected function mock($abstract, Closure $mock = null)
{
// This also returns MockInterface.
return $this->singletonInstance($abstract, Mockery::mock(...array_filter(func_get_args())));
} So my original usage is valid. |
@mpyw I'm sorry but I still believe you're not correctly using that. Please try a support channel. |
I'll appreciate if you investigate my issue when you have a moment. Thanks |
This is a simple custom Form Request; <?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class TestRequest extends FormRequest
{
public function rules(): array
{
return [
'key' => [
'required',
'string',
],
];
}
} The following test was run using Laravel 8.47.0. <?php
namespace Tests\Unit;
use App\Http\Requests\TestRequest;
use Illuminate\Routing\Controller as BaseController;
use Mockery;
use Mockery\MockInterface;
use Tests\TestCase as BaseTestCase;
/** @group mock */
class MockSingletonTest extends BaseTestCase
{
/** @test */
public function usingInstance(): void
{
$data = [
'key' => $this->faker->word,
];
/** @var \App\Http\Requests\TestRequest $request */
$request = $this->instance(
TestRequest::class,
Mockery::mock(TestRequest::class, static function (MockInterface $mock) use ($data): void {
$mock->shouldReceive('validated')->andReturn($data);
})
);
$controller = $this->controller();
$response = $this->app->call($controller, [
$request,
]);
$this->assertSame('8.47.0', $this->app->version());
$this->assertSame($data, $request->validated());
$this->assertSame($data, $response);
}
/**
* @test
* This test throws \Illuminate\Validation\ValidationException
* It should work the same way as above.
*/
public function usingPartialMock(): void
{
$data = [
'key' => $this->faker->word,
];
/** @var \App\Http\Requests\TestRequest $request */
$request = $this->partialMock(
TestRequest::class,
static function (MockInterface $mock) use ($data): void {
$mock->shouldReceive('validated')->andReturn($data);
}
);
$controller = $this->controller();
$response = $this->app->call($controller, [
$request,
]);
$this->assertSame('8.47.0', $this->app->version());
$this->assertSame($data, $request->validated());
$this->assertSame($data, $response);
}
protected function controller(): BaseController
{
return new class extends BaseController {
public function __invoke(TestRequest $request): array
{
return $request->validated();
}
};
}
}
I then updated to Laravel 8.48.2 and re-run the test (changing the version number). <?php
namespace Tests\Unit;
use App\Http\Requests\TestRequest;
use Illuminate\Routing\Controller as BaseController;
use Mockery;
use Mockery\MockInterface;
use Tests\TestCase as BaseTestCase;
/** @group mock */
class MockSingletonTest extends BaseTestCase
{
/** @test */
public function usingInstance(): void
{
$data = [
'key' => $this->faker->word,
];
/** @var \App\Http\Requests\TestRequest $request */
$request = $this->instance(
TestRequest::class,
Mockery::mock(TestRequest::class, static function (MockInterface $mock) use ($data): void {
$mock->shouldReceive('validated')->andReturn($data);
})
);
$controller = $this->controller();
$response = $this->app->call($controller, [
$request,
]);
$this->assertSame('8.48.2', $this->app->version());
$this->assertSame($data, $request->validated());
$this->assertSame($data, $response);
}
/**
* @test
* This test throws \Illuminate\Validation\ValidationException
* It should work the same way as above.
*/
public function usingPartialMock(): void
{
$data = [
'key' => $this->faker->word,
];
/** @var \App\Http\Requests\TestRequest $request */
$request = $this->partialMock(
TestRequest::class,
static function (MockInterface $mock) use ($data): void {
$mock->shouldReceive('validated')->andReturn($data);
}
);
$controller = $this->controller();
$response = $this->app->call($controller, [
$request,
]);
$this->assertSame('8.48.2', $this->app->version());
$this->assertSame($data, $request->validated());
$this->assertSame($data, $response);
}
protected function controller(): BaseController
{
return new class extends BaseController {
public function __invoke(TestRequest $request): array
{
return $request->validated();
}
};
}
}
The error is as below;
|
I got it!
// Without Mocking
Mail::send($mailable);
↓
$mailManager = Mailer::getFacadeRoot();
$mailManager->send($mailable);
↓
$mailManager = Mailer::getFacadeRoot();
$mailManager->__call('send', [$mailable]);
↓
$mailManager = Mailer::getFacadeRoot();
$mailManager->mailer()->send($mailable); // With Mocking
Mail::send($mailable);
↓
$mailManagerMock = Mailer::getFacadeRoot();
$mailManagerMock->send($mailable);
↓
// Mockery can't find `Mailer::send()` method under `MailManager::__call()` delegation. How do we get correct approach (not workarounds) for this problem...? @taylorotwell |
Is this ticket no longer being looked at? It was closed before I provided my test case and the merged code actually solved a different bug/inconsitency. |
I'll re-open this to have another look. |
I can't figure it out. I'm just going to send in a PR to revert the PR that's causing this. |
We're tagging a new release for this. |
The current example is essentially doing this: $this->app->call($controller); The $this->app->call($controller, [
'request' => $request,
]); This will prevent the request object from being resolved out of the container. If it's important to resolve the test object out of the container (for the purpose of what's being tested), there's another underlying problem with the way this is being setup. Under non-test circumstances, In order to resolve the
Edit: Sorry it took so long to get back on this. Was away. |
Thanks @Nacoma. I'm gonna let this one rest. Anyone can attempt a new PR if they want which isn't breaking. |
Description:
In a Unit test for a Controller I mocked the request using the following;
Which gave a valid result until updating Laravel from 8.47 to 8.48.
It seems that the introduction of the
singletonInstance
in the commit bind mock instances as singletons so they are not overwritten and the use of that in themock()
,partialMock()
andspy()
methods of theInteractsWithContainer
trait. This seems to mean that an emptyGET
request is always used.I have been able to resolve my failing tests by going back to the longer version of the code to mock an object, as found in the documentation.
Steps To Reproduce:
returns different
$request
compared toThe text was updated successfully, but these errors were encountered: