Laravel8 – Mocking methods from factory relationship models

I am trying to test a specific part of my code, but some dependencies of a Model class make this test very difficult to perform. If those dependencies were on a class that is injected, I could easily mock the class. Is it possible to do something similar when it comes from a database relationship from a factory? I am converting the idea to the example in Laravel’s documentation, to try to make it easier to exemplify :

use AppModelsPost;
use AppModelsUser;

$user = User::factory()
            ->has(Post::factory()->count(3))
            ->create();

Let’s say that I am testing a controller that will create comments for posts of an user. I’ll use a factory to create the user and post and perform a test post call to the route /api/comments, this post has the fields post_id (int) and comment (text).
However, the Comments controller will call a method Post::canAddComment(), that will perform a lot of verifications to validate if the comment can be created. All those validations are out of the scope of my test.

Is it possible to use a Mock of the Post model class, so I can make, for example:

$postMock->shouldReceive('canAddComment')->once()->andReturn(true);

So that I don’t need to be creating a whole scenario to be able to do the test?

Possible solution:

Extend the Post class, for tests only:

class PostThatAlwaysAllowsComments extends Post
{
    public function canAddComment() {
        return true;
    }
}

And then in the tests:
use AppModelsPost;
use AppModelsUser;

$user = User::factory()
            ->has(PostThatAlwaysAllowsComments::factory()->count(3))
            ->create();

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

In Laravel, mocking models is not really a standard or neither convenient. Most of the times, you arrange the data and let’s model do what models does. In your case you can utilise a wrapper / proxy aproach, to easiler be able to mock your calls. This will also act as the entry point for mocking, as you need a class in the container to be mocked.

class PostThatAlwaysAllowsComments extends Post
{
    public function canAddComment() {
        resolve(CommentService::class)->comment($this);
    }
}

There is used resolve as to fetch the service from the container, as the new keyword will not work.

Now using Laravels mock syntaxic sugar, you can mock the underlying service and avoid triggering the code you do not care about and asserting it is actually called.

namespace TestsUnit;

use AppServicesCommentService;
use MockeryMockInterface;
use TestsTestCase;

class PostAddComentTest extends TestCase
{
    public function testDoWork()
    {
        $post = // create your model with the factory;

        $mock = $this->mock(CommentService::class, function (MockInterface $mock) {
            $mock->shouldReceive('comment')
                ->once()
                ->andReturn(true);
        });

        // call api or method
    }
}


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x