Home > Software engineering >  How to mock Laravel model for a test of a class?
How to mock Laravel model for a test of a class?

Time:10-08

I have a class like this:

<?php 
use App\Models\Product;

class ProductSearcher
{
    public function getPrintedProducts( $releasedOnly = true ) {
        $products = Product::with('productVersion','productVersion.tag')
                      ->where('printed', 1);
        if ($releasedOnly)
            $products->where('released', 1);
        $products = $products->get();
        return $products;
    }
}

In my testing, I write

// ... some other testing ...
public function testChossingCorrectingProductFromProductVersionIds()
{
    $productSearcher= new ProductSearcher;

    $productMock = \Mockery::mock( 'App\Models\Product' );
    $productMock->shouldReceive( 'with->where->where->get' )->andReturn( [] );

    $this->assertEmpty( $productSearcher->getPrintedProducts( true ) );
}
// ... some other testing ...

I run the test but get:

PHP Fatal error:  Cannot redeclare Mockery_1_App_Models_Product::mockery_init()

May I ask what have I done wrong? Or what is the proper way to test this function? (It is a unit test that should not be too heavy, so connecting to a testing database is not an option)

CodePudding user response:

You have to use a factory to "mock" models, you don't mock core classes, you just fake them. You have to create a model or models with the data you should expect to have at that moment.

CodePudding user response:

Best way, imo, to test that kind of functionality is setting a test database in memory. Then, when it's empyt, creating during test couple of models using factories. Then you known how much of database records you have and what kind. Then just check if class returns right ones.

Second way is using IoC - refactor ProductSearcher and pass Product model class as dependency. Then you can easily mock class during tests.

But i have another question - why you created ProductSearcher class in the first place? Wouldn't be easier to use scope instead?

If you wanted to use factories this is how to do that:

  • First - define factory. For main model and its relationships if needed. For convenience you can use states to create predefined sets of values in records. https://laravel.com/docs/8.x/database-testing#defining-model-factories

  • Second - create a test. My example:

      public function testChossingCorrectingProductFromProductVersionIds()  
     {  
      $productSearcher= new ProductSearcher(); 
    
      $notPrinted = Product::factory->count(2)->create([  
          'printed' => false,  
          'released' => false  
      ]);  
    
      $printed = Product::factory->count(3)->create([  
          'printed' => true,  
          'released' => false  
      ]);   
    
      $releasedNotPrinted = Product::factory->count(4)->create([  
          'printed' => false,  
          'released' => true  
      ]);
    
      $releasedPrinted = Product::factory->count(5)->create([  
          'printed' => true,  
          'released' => true  
      ]);  
    
      $this->assertCount(8, $productSearcher->getPrintedProducts( false ) );  
      $this->assertCount(5, $productSearcher->getPrintedProducts( true ));  
    

    }

Some developers are over sensitive about "one test, one assert", but i'm not one of them, as you can see :D

  • Related