Is the code actually executed when using a Mockery spy? - laravel

I am trying to write some tests with a Mockery Spy. However, it doesn't seem like the "spied upon" code is executed when I'm using a spy.
Is the code actually executed when using a Mockery spy?
Here is what I tested:
// In my test:
$spy = $this->spy(FeedManager::class);
// Controller
resolve(FeedManager::class)->createResponse();
// FeedManger::createResponse()
public static function createResponse(Builder $builder)
{
dd("here i am"); // this never gets called unless I remove the spy
}

Apparently, the "spy code" is not supposed to run. Found this quote from the docs.
The \Mockery::spy() method call is actually a shorthand for calling
\Mockery::mock()->shouldIgnoreMissing(). The shouldIgnoreMissing
method is a “behaviour modifier”.

Related

Mocking out a function inside another function

I don't actually want 'anotherFunction' to execute.
But rather when it's called inside someFunction, to have it return a specific value rather than actually executing:
// Testing this function
export function someFunction(foo, bar): string {
// want to provide a mock result here
const baz = anotherFunction(foo, bar);
// do something unrelated
}
export function anotherFunction(quz, quux): any {
// do something unrelated
}
How would you go about this with jasmine? The examples I find all assume a class and then use:
// Can't use this as the method I'd like to "mock out" is not in a class
const spy = spyOn(someClass, 'aMethod');
I'm looking for something similar to the mock function in Jest. That documentation helps communicate my question better:
"Mock functions allow you to test the links between code by erasing the actual implementation of a function, ..."
But then something similar in Jasmine.
Try something like this:
import * as helpers from './file/where/anotherFunction/is';
...
spyOn(helpers, 'anotherFunction');
Check this link out.

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.)
}

Mock out function call to another function in same class

I have function that calls out to another function in the same class. I'm trying to test if that method is called in my unit tests however can't find a way to mock it.
Here is an example of the method call
public function follow($user_id, $followsfeed, $target_user_id)
{
$news_feeds = $this->getNewsFeeds($user_id);
}
I want to mock the method getNewsFeed and assert it is being called when I execute follow.
What is the best way to achieve this. as I can't workout how to create an internal mocked object.
You can use a partial mocks, as example on your test class, you can do:
public function test_follow()
{
$mock = Mockery::mock('App\Services\SomeService')->makePartial();
$mock->shouldReceive('getNewsFeeds')
->once()
->with("id1","id2")
->andReturn($someNews);
$mock-> follow("id1","id2");
}
You can also use the PHPUnit test-doubles
Hope this help

Mockery: Method does not exist on mock object

So I have an object which I am mocking and it has methods that are used for sending emails. I have gone from a static class over to a concrete class because I was having issues testing the static class with Mockery.
However I am now finding that when an eloquent model is saved it throws an event which fires off a listener. This listener is what is responsible for kicking off the call to the mock objects method.
I have found that when I do something like the following it works.
$model = factory(MyClass::class)->make();
$model->property = 'value';
$model->save()
$this->mailer->shouldHaveReceived('methodName')->with($arg1, $arg2, $arg3);
It will fail every time complaining that the method does not exist on the mock object, the worst part is if I go into my listener where the mock object method is called and I do something like this
echo '<pre>'.print_r(get_class_methods($this->mailer), true).'</pre>'; exit;
it shows that the method is in-fact in the array of methods returned from get_class_methods.
Now here is the kicker. Everything works perfectly and the test passes without any errors if I do the following:
$this->mailer->shouldReceive('methodName');
$model = factory(MyClass::class)->make();
$model->property = 'value';
$model->save()
Now I have been reading the Mockery Gotchas and it would appear that if my class was calling a method that truly didn't exist on the object via ___call then this error would be expected.
However my mailer class is NOT extending any other class that would have a __call method nor does it have that method defined on the class.
However as you may or may not know Eloquent Models do in-fact use the magic ___call method. However I am NOT mocking the model with Mockery, I am using factory models in Laravel but I am also not checking the model object for the method call, I am checking my concrete mailer class for the method call.
Anyone know why I might be getting this behavior from Mockery?
I am creating my mock object in Laravel 5.2 with the following code
public function mock($class)
{
$mock = Mockery::mock($class);
$this->app->instance($class, $mock);
return $mock;
}
$this->mailer = $this->mock('Namespace\Classname');
How are you creating $this->mailer? In order to be able to use the spy functionality ->shouldHaveReceived, you need to have invoked Mockery::spy($className) or called the shouldIgnoreMissing method after creating the test double with Mockery::mock($className).

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