How to register a mock object in Service Container testing - laravel

I need to register a object to the service container.
I tried with the following code but I get
Error Call to undefined method ::process()
$o = $this->instance(Service::class, Mockery::mock(Service::class, function ($mock) {
$mock->shouldReceive('process')->once()->andReturn(10);
}));
$this->app->instance(Service::class, $o);
dd((new Service())->process());

Firstly for mocking an object, this should suffice. The middle step is not required.
$this->instance(Service::class, Mockery::mock(Service::class, function ($mock) {
$mock->shouldReceive('process')->once()->andReturn(10);
}));
For your mock to load, you have to get it out through the container, you have recently bound it with the container. There are many ways of doing this, $this->app->instance(), app(), resolve() etc.
dd($this->app->instance(Service::class)->process());

Related

Mockery - creating mock with constructor data

I'm trying to use Laravel's app service container to resolve out mocked instances for testing. I've created a mock which works when making an instance of CS_REST_Subscribers alone, however if I provide arguments to the service container my mock no longer applies.
$this->mock(\CS_REST_Subscribers::class, function (MockInterface $mockery) {
$mockery
->shouldReceive('add')
->once();
});
get_class(app()->make(\CS_REST_Subscribers::class)); // returns Mockery_2_CS_REST_Subscribers
get_class(app()->make(\CS_REST_Subscribers::class, [
'list_id' => 'testing',
'auth_details' => ['api_token' => '123']
])); // returns CS_REST_Subscribers
Dump 1 gives me Mockery_2_CS_REST_Subscribers but dump 2 gives me CS_REST_Subscribers.
Any idea how to apply the mock even when passed constructor arguments? I can't help but feel like I'm missing something here...
I've just found the solution off the back of a Laravel raised issue https://github.com/laravel/framework/issues/19450#issuecomment-451549582
It seems that when passing parameters, Laravel's built in mocking bypasses building a mock instance.
The solution was to create my Mockery mock and then bind it to the service container directly, thus forcing Laravel to resolve what it has been given in the service container.
$mock = \Mockery::mock(\CS_REST_Subscribers::class)->makePartial();
$mock->shouldReceive('add')->once();
$this->app->bind(\CS_REST_Subscribers::class, fn() => $mock);

Laravel unit testing automatic dependency injection not working?

With Laravel Framework 5.8.36 I'm trying to run a test that calls a controller where the __construct method uses DI, like this:
class SomeController extends Controller {
public function __construct(XYZRepository $xyz_repository)
{
$this->xyz_repository = $xyz_repository;
}
public function doThisOtherThing(Request $request, $id)
{
try {
return response()->json($this->xyz_repository->doTheRepoThing($id), 200);
} catch (Exception $exception) {
return response($exception->getMessage(), 500);
}
}
}
This works fine if I run the code through the browser or call it like an api call in postman, but when I call the doThisOtherThing method from my test I get the following error:
ArgumentCountError: Too few arguments to function App\Http\Controllers\SomeController::__construct(), 0 passed in /var/www/tests/Unit/Controllers/SomeControllerTest.php on line 28 and exactly 1 expected
This is telling me that DI isn't working for some reason when I run tests. Any ideas? Here's my test:
public function testXYZShouldDoTheThing()
{
$some_controller = new SomeController();
$some_controller->doThisOtherThing(...args...);
...asserts...
}
I've tried things like using the bind and make methods on app in the setUp method but no success:
public function setUp(): void
{
parent::setUp();
$this->app->make('App\Repositories\XYZRepository');
}
That's correct. The whole idea of a unit test is that you mock the dependant services so you can control their in/output consistently.
You can create a mock version of your XYZRepository and inject it into your controller.
$xyzRepositoryMock = $this->createMock(XYZRepository::class);
$some_controller = new SomeController($xyzRepositoryMock);
$some_controller->doThisOtherThing(...args...);
This is not how Laravels service container works, when using the new keyword it never gets resolved through the container so Laravel cannot inject the required classes, you would have to pass them yourself in order to make it work like this.
What you can do is let the controller be resolved through the service container:
public function testXYZShouldDoTheThing()
{
$controller = $this->app->make(SomeController::class);
// Or use the global resolve helper
$controller = resolve(SomeController::class);
$some_controller->doThisOtherThing(...args...);
...asserts...
}
From the docs :
You may use the make method to resolve a class instance out of the
container. The make method accepts the name of the class or interface
you wish to resolve:
$api = $this->app->make('HelpSpot\API');
If you are in a location of your code that does not have access to the
$app variable, you may use the global resolve helper:
$api = resolve('HelpSpot\API');
PS:
I am not really a fan of testing controllers like you are trying to do here, I would rather create a feature test and test the route and verify everything works as expected.
Feature tests may test a larger portion of your code, including how
several objects interact with each other or even a full HTTP request
to a JSON endpoint.
something like this:
use Illuminate\Http\Response;
public function testXYZShouldDoTheThing()
{
$this->get('your/route')
->assertStatus(Response::HTTP_OK);
// assert response content is correct (assertJson etc.)
}

How to resolve a contextually bound class from Laravel IOC container

I have a command class with a dependency type-hinted in the constructor:
class RunAnalyticsCommand extends Command
{
public function __construct(Analytics $analytics)
{
//
}
}
The constructor for the Analytics class looks like this:
class Analytics
{
public function __construct(Odoo $odoo, ChannelInterface $channel)
{
//
}
}
In a service provider, I've instructed the application what to instantiate for the Odoo class. If I create an instance of Analytics like this, it works fine. It gets the Odoo instance from the container and uses the channel that I pass in.
$analytics = app(Analytics::class, ['channel' => new ChannelA]);
Now, I'm trying to use contextual binding to accomplish all of this "behind the scenes". I write the following in my service provider's register method:
$this->app->when(RunAnalyticsCommand::class)
->needs(Analytics::class)
->give(function () {
return app(Analytics::class, ['channel' => new ChannelA]);
});
However, now when I run the RunAnalyticsCommand, I get an error that Maximum function nesting level of '256' reached, aborting!
I'm assuming this happens because the give callback is trying to resolve the same Analytics class, and the container treats that call as if it was also coming from the RunAnalyticsCommand class, so it just keeps trying to resolve the same thing over and over.
Is this the expected behavior or a bug with contextual binding? Shouldn't the call to resolve the class from within the give callback not behave as if it were originating from the RunAnalyticsCommand? Is there some other way to tell the container to resolve without using the contextual binding?

angular $http service singleton clash

$http service in angular is singleton, and my previous experience with xhr/ ajax request shown to have clash when two request share the same xhr object. Should it not a problem with angular ? if yes how angular handles such situation ?
I think you're misunderstanding the fact that the $http service is a singleton to mean that all requests somehow will share the same XHR object. They don't.
The $http service itself is a singleton, but that doesn't mean that the requests share the same XHR object.
Anytime you call an $http service method (for example, $http#get), it initializes a new asynchronous request... However, it doesn't initialize a new $http object.
Take a look at some of Addy Osmani's sample code for the singleton pattern:
return {
getInstance: function () {
if ( !instance ) {
instance = init();
}
return instance;
}
};
The singleton pattern simply ensures that a new instance of the $http service itself doesn't get initiliazed over and over again... But it doesn't mean there is just one XHR object.
The pseudo-code for the $http service would look something like this:
var $httpProvider = (function() {
var somePrivateConfig = "something important";
var service = {
request: function() {
// make an HTTP request with an XHR object
}
};
return {
init: function() {
// this is the important part that makes sure its a singleton
window.$http = window.$http || service;
}
};
})();
/**
* Something like this would be called whenever you
* inject the $http service as a dependency...
* However, since it could be passed into multiple things all in the same runtime,
* like controllers, directives, etc., we only need to initialize it ONE time
*/
$httpProvider.init();
/**
* Now let's pretend we're inside, say, a controller.
*
* This method can safely be called many times,
* but the method is always being called from the same singleton object
*/
$http.request();
Also, you'll notice that there's a local variable somePrivateConfig within the $httpProvider IIFE. If we were to re-initialize a new $http every time its injected to a component (whether that's a controller, directive, or whatever), a new private variable would be created, but we always want to be referencing that same value throughout the lifecycle of the $http object, so that we can guarantee that all those components are always referencing the same information.
But this has nothing to do with the XHR object itself. I may have misused some of the terminology above and misrepresented where and how providers themselves within the context of AngularJS are initialized into singleton objects, but the principle of the singleton pattern still stands in that it simply means the async request wrapper "class" (which is the $http service) is a singleton, but the XHR isn't.
$http requests are async and return a promise with methods success() and error(). Below more information ($q service, which is an implementation of promises by Angularjs):
"A service that helps you run functions asynchronously, and use their return values (or exceptions) when they are done processing"
Read here:
https://docs.angularjs.org/api/ng/service/$http
https://docs.angularjs.org/api/ng/service/$q

Laravel core method confusion

I have been digging in the core of Laravel because I would like to understand how it works. But I come up with a method that I just cannot wrap my head around even after 3 days.
In start.php the app is binded to itself. So far so good. But when I check the $app->share method I am lost.
public function share(Closure $closure)
{
return function($container) use ($closure)
{
// We'll simply declare a static variable within the Closures and if
// it has not been set we'll execute the given Closure to resolve
// the value and return it back to the consumers of the method.
static $object;
if (is_null($object))
{
$object = $closure($container);
}
return $object;
};
}
This method returns an anonymous function which when executed returns an instance of the app. Do I see that right? Why is this? Why do you want to return a closure and not just the instance. This seems like a strange way, but I am quite sure that there is a reason ;) ??
UPDATE
The line in start.php:
$app['app'] = $app->share(function($app) { return $app; });
So I would think that $app['app'] is a closure object. However if I do get_class the class is Illuminate\Foundation\Application .
Furthermore there is also no way to execute it as $app'app' will not work obviously.
$app is no normal array, it is actually an instance of Illuminate\Foundation\Application1, an extension of Illuminate\Container\Container2, which implements ArrayAccess. But you know this already, as that's where the share() method lives.
The container binds keys to closures, when the keys are accessed the value is fetched from memory or, on first access, the bound closure is called and the resulting value is returned. When a key is set on the container it is wrapped in a closure unless it is already a closure.
This provides a consistent internal interface for the container, so that the code is not constantly type checking its contents. It will also only load the references you actually use are into memory - it is thought that the footprint of a closure is lighter than that of a fully loaded class instance. But once loaded, you get the benefit of working with the same instance for the rest of the request.
Why the app is not registered on the container using instance() I don't know though - perhaps it produces recursive references in trace and dump output.

Resources