Mockery - creating mock with constructor data - laravel

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);

Related

Can I change this feature test to a unit test?

I have written two tests: one that makes a post request to an endpoint and awaits for a specific response containing status and message; and another one making the exact same request, but instead of await a response, it verifies if the database has the data matching what I just sent. Both these test are feature tests, and so far I have no unit test in my application; that happens because I have tested endpoint only.
So my idea is the following: instead of making a call to an endpoint in my second test, I could directly test my service method that creates a new register to the database. Would this be a valid unit test?
Personally, I think it would be valid because I am isolating a specific method and testing if the code works, and not if the integration works, even though there's integration of my code with the DB (Eloquent), my service method is the closest testable thing to the DB I have in my system.
My two tests, in the order I specified above:
/** #test */
public function a_group_can_be_created()
{
$this->withoutExceptionHandling()->signIn();
$group_data = [
'name' => $this->faker->word(),
'status' => $this->faker->boolean(),
];
$modules = ['modules' => Modules::factory(1)->create()->pluck('id')];
$response = $this->post(route('cms.groups.store'), array_merge($group_data, $modules));
$response->assertSessionHas('response', cms_response(trans('cms.groups.success_create')));
}
/** #test */
public function creating_a_group_persists_its_data_to_the_database()
{
$this->withoutExceptionHandling()->signIn();
$group_data = [
'name' => $this->faker->word(),
'status' => $this->faker->boolean(),
];
$modules = ['modules' => Modules::factory(1)->create()->pluck('id')];
$this->post(route('cms.groups.store'), array_merge($group_data, $modules));
$this->assertDatabaseHas('groups', $group_data);
$this->assertDatabaseCount('modules', 2);
$this->assertDatabaseCount('group_modules', 2);
}
Unit test in laravel "do not boot your Laravel application and therefore are unable to access your application's database or other framework services"
With that said, you can't access any database with a facade or with your eloquent model
so if you change your feature testing to unit testing, it will fail.
Unit test will work well if you don't use any of laravel framework utilities. I occasionally use it for testing a little self made library
But if you want to isolate it and create feature testing without calling the API endpoint, it will works too. It's really up to you to decide whether it is necessary to do that or not. But keep in mind that unnecessary test will make the test longer, especially if you use RefreshDatabase or DatabaseMigration trait. It will quite annoying to wait for them to finish

How to register a mock object in Service Container testing

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());

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?

Laravel Stripe Mocking

How can we mock Stripe in Laravel Unit Tests without using any external package like stripe-mock etc?
The job is to test the Controller feature where the secret is hardcoded and due to which test is failing.
Hey i ran into the same problem,
i am using aspectmock from codeception. Gave me some grief setting it up but im now able to mock all the responses with a json response. this way the json data goes thru the stripe classes and it throws the correct errors and returns the same objects.
hope that helps
https://github.com/Codeception/AspectMock
public function testAll()
{
$customerClass = new StripeCustomers();
test::double('Stripe\HttpClient\CurlClient', ['request' => [json_encode($this->allJsonData), 200, []]]);
$customer = $customerClass->all();
$this->assertArrayHasKey('data', $customer);
}

ZF2: Injecting Session management into a service

I'm familiar with how to use the Session in ZF2, e.g.
$user_session = new Container('user');
$user_session->username = 'JohnDoe';
This is fine, but if I'm trying to persist session data in one of my business logic services I'd strongly prefer to inject a session management object/service into my service's constructor, like in this pseudocode:
class BusinessSvc{
protected $sessionSvc;
function __construct($sessionSvc){
$this->sessionSvc = $sessionSvc;
}
public function doBusinessLayerStuff(){
... do stuff ...
$this->sessionSvc->store('lastOrderNumber', '1234');
... do stuff ...
}
}
I would think the framework would provide this functionality, but I can't find it anywhere. I could always write my own, but didn't want to reinvent the wheel.
The answer was a lot simpler than I realized. Once instantiated, a Container instance itself can be injected into the business service and provide it with access to the session. If using phpunit to later test the service, the object could be mocked with an array or an instance of ArrayObject.
In Module.php's getServiceConfig method:
'MyModule\Service\BusinessService' => function($sm) {
// Container doesn't need to use this name but it seems sensible.
$container = new Container('MyModule\Service\BusinessService');
return new Service\BusinessService($container);
},

Resources