4

I'm trying to write some tests using Pest and mocking my model. I have a Repository that accepts two models, this is the definition:

class MyRepo
{
    public function __construct(private ModelA $modelA, private ModelB $modelB)
    {
      //
    }
}

Now in my test i created two mock for ModelA and ModelB, and created the repository:

$modelA = mock(ModelA::class);
$modelB = mock(ModelB::class);

$repo = new MyRepo($modelA, $modelB);

Running this code I get a check error like this:

__construct(): Argument #1 ($modelA) must be of type App\Models\ModelA, Pest\Mock\Mock given

If I remove the type check on Repository constructor, all works fine.

I tried to find a solution, but for now, I have had no luck. I'm not able to understand what i need to change to make it works

Rain
  • 3,416
  • 3
  • 24
  • 40
Mistre83
  • 2,677
  • 6
  • 40
  • 77
  • 2
    Why are you mocking a model ? is it literally a Laravel Model ? If so you NEVER mock a Laravel's Model but you use [Factories](https://laravel.com/docs/8.x/database-testing#defining-model-factories). – matiaslauriti Oct 30 '21 at 01:20

2 Answers2

2

As the comment states, don't mock models, this is not the intended use of the testing tools in Laravel. In other words, i have written thousands of tests and not once i had to mock the model.

Having Models as parameters to a constructor, wouldn't make sense. As Laravels container would often just resolve them to empty models, with resolve(MyRepo::class); and the container is an essential tool for Mocking.

With that said, the problem you are facing is, mock() returns the Pest Mock object and not the Mockery object, this object is located here on the Pest Mock Class. This can only be returned by calling expect(). Therefor utilize expect(), with the operations you need to call on your mock and your code should work.

mock(ModelA::class)
    ->shouldReceive('update')
    ->once()
    ->andReturn(true);
mrhn
  • 17,961
  • 4
  • 27
  • 46
  • Thanks, sorry but i'm new to mocking objects and i gets a little confused. What you mean with "Having Models as parameters to a constructor, wouldn't make sense" ? I have those two models on constructor because it's the repository, in my app i never create those model, i use DI. Its wrong ? – Mistre83 Nov 02 '21 at 08:17
  • 1
    Thats correct, i was afraid you wanted to inject concrete models into the consctrutor. Like new YourRepo(ModelA::find(1), ModelB::find(2));, thats incorrect, but if you use DI thats totally correct. It's often a thing people struggle with in mocking and testing, i thought the same here due to the code shown. But you are totally correct mate. – mrhn Nov 02 '21 at 08:50
0

Looking at your answer now I changed my code using a factory, and I pass that factory to the repository. This is the right way?

The getUser is the method i want to test, so i create the two model with a factory. It's right ?

// prepare
$modelA = ModelA::factory()->create();
$modelB = ModelB::factory()->create();

$repository = new MyRepo($modelA, $modelB);
$service = new MyService($repository);

// act
$user = $service->getUser("fake");
Mistre83
  • 2,677
  • 6
  • 40
  • 77