theferrett's avatar

Mocking A Static Eloquent Model?

Currently, I have a bunch of static helper methods with a fair amount of logic that requires testing.

Buried in those numerous methods is a single database call used in a single helper function:

Categories::where('x', 'y')->first().

I currently do not want to go through all the trouble of configuring a whole sqlite database and populating it with seeder data (our .env files are pretty custom), OR to pass a $Category class into every instance of this function to fake it. (Although they either is a possible last resort.)

Ideally, what I'd like to be able to do it something like:

    $mock = Mockery::mock('alias:Category');
    $mock->shouldreceive('first')->andReturn('someObject');

    $this->assertequals(Category::first, 'someObject');

But you know, with that actually working. Is there an easy way?

0 likes
6 replies
MohamedTammam's avatar

If it's static methods, then you don't need mocking. However, how do you want to generate a data without model factories and seeders?

theferrett's avatar

@MohamedTammam Since there's only one database call (and the logic is dependent on two different values in one database field), I want to mock the Category object to return one value when queried across two tests - in this test, this column is TRUE, .in the other this column is FALSE. That's it.

martinbean's avatar

@theferrett When testing becomes difficult, it usually hints to your design being too complicated, or that it could benefit from a little re-factor.

In your case, if you’re querying for a category in lots of places then I’d move that query to a repository-like class, that’s then resolved by the container. If it’s resolved by the container, you can then mock it far more easily:

class CategoryRepository
{
    public function getSuperImportantCategory(): Category
    {
        return Category::query()->where('x', 'y')->first();
    }
}
$this->mock(CategoryRepository::class, function (MockInterface $mock) {
    $mock->shouldReceive('getSuperImportantCategory')->once()->andReturn(new Category([
        // Attributes for mocked, in-memory Category model here
    ]));
});

If you’re resolving it for the container, you can also extend the binding to wrap it in a caching layer if you’re querying for the same category frequently.

theferrett's avatar

@martinbean "In your case, if you’re querying for a category in lots of places"

Unfortunately, I'm not querying for a category in lots of places OR frequently. As I said: "Buried in those numerous methods is a single database call used in a single helper function." It'd be for two tests total.

That said, even if this does apply, I'm not sure why that doesn't appear to work for mocking the static Category model call in the first place.

I'm open to potentially refactoring, but not sure why a single database call in a static helper method (which only translates changing external API data to our values) would be a bad code smell in Laravel.

martinbean's avatar

@theferrett The “code smell” has already revealed itself and is the reason for your question: it’s hard to test because it’s a static method call buried inside various other methods.

theferrett's avatar

@martinbean I may be dense, but I'm failing to see how things would be done in the Laravel way if:

  1. You have a bunch of static methods designed to translate API values into our native values;
  2. One of those methods required you to compare the API value against a local database value to see how to transform it.

Are you saying that "Static methods should not be used because Laravel hates testing them?" Are you saying that "Static methods shouldn't have database calls within them," and if so, why?

If you're saying it's a bad code smell I'm willing to believe you, but what's the alternative approach that will supposedly be cleaner?

Please or to participate in this conversation.