Reputation: 5030
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)
Upvotes: 0
Views: 3970
Reputation: 8082
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.
For you to correctly test your code, you should have code like this:
namespace Tests\Unit;
use App\Models\Product;
use App\Models\ProductVersion;
use App\Models\Tag;
use App\ProductSearcher;
use Tests\TestCase;
class ProductSearcherTest extends TestCase
{
public function test_correct_products_get_returned_when_released_only_is_true(): void
{
$firstProduct = Product::factory()
->has(
ProductVersion::factory()
->has(Tag::factory())
)
->create([
'printed' => 1,
'released' => 1,
]);
$secondProduct = Product::factory()
->has(
ProductVersion::factory()
->has(Tag::factory())
)
->create([
'printed' => 1,
'released' => 1,
]);
Product::factory()
->has(
ProductVersion::factory()
->has(Tag::factory())
)
->create([
'printed' => 1,
'released' => 0,
]);
Product::factory()
->has(
ProductVersion::factory()
->has(Tag::factory())
)
->create([
'printed' => 0,
'released' => 0,
]);
$products = (new ProductSearcher)->getPrintedProducts(true);
$this->assertCount(2, $products);
$this->assertTrue(
$products->first()->is($firstProduct),
'The first product is not the expected one.'
);
$this->assertTrue(
$products->first()->productVersion->is($firstProduct->productVersion),
'The first product does not have the expected Product Version associated.'
);
$this->assertTrue(
$products->first()->productVersion->tag->is($firstProduct->productVersion->tag),
'The first product does not have the expected Tag associated.'
);
$this->assertTrue(
$products->last()->is($secondProduct),
'The last product is not the expected one.'
);
$this->assertTrue(
$products->last()->productVersion->is($secondProduct->productVersion),
'The last product does not have the expected Product Version associated.'
);
$this->assertTrue(
$products->last()->productVersion->tag->is($secondProduct->productVersion->tag),
'The last product does not have the expected Tag associated.'
);
}
public function test_correct_products_get_returned_when_released_only_is_false(): void
{
$firstProduct = Product::factory()
->has(
ProductVersion::factory()
->has(Tag::factory())
)
->create([
'printed' => 1,
'released' => 0,
]);
$secondProduct = Product::factory()
->has(
ProductVersion::factory()
->has(Tag::factory())
)
->create([
'printed' => 1,
'released' => 0,
]);
Product::factory()
->has(
ProductVersion::factory()
->has(Tag::factory())
)
->create([
'printed' => 1,
'released' => 1,
]);
Product::factory()
->has(
ProductVersion::factory()
->has(Tag::factory())
)
->create([
'printed' => 0,
'released' => 1,
]);
$products = (new ProductSearcher)->getPrintedProducts(true);
$this->assertCount(2, $products);
$this->assertTrue(
$products->first()->is($firstProduct),
'The first product is not the expected one.'
);
$this->assertTrue(
$products->first()->productVersion->is($firstProduct->productVersion),
'The first product does not have the expected Product Version associated.'
);
$this->assertTrue(
$products->first()->productVersion->tag->is($firstProduct->productVersion->tag),
'The first product does not have the expected Tag associated to the Product Version.'
);
$this->assertTrue(
$products->last()->is($secondProduct),
'The last product is not the expected one.'
);
$this->assertTrue(
$products->last()->productVersion->is($secondProduct->productVersion),
'The last product does not have the expected Product Version associated.'
);
$this->assertTrue(
$products->last()->productVersion->tag->is($secondProduct->productVersion->tag),
'The last product does not have the expected Tag associated to the Product Version.'
);
}
}
Let me explain:
ProductSearcher
is either true
or false
.
released = 1
, but we also create some that have released = 0
so we make sure those are not being got too. We also create at least one with released = 0
so we are also not getting that one.released
). We want to make sure we are getting all that has printed = 1
but released = 0
.$releasedOnly = true
-> Model: printed = 1, released = 1
.$releasedOnly = false
-> Model: printed = 1, released = 0
.ProductVersion
and Tag
models related to each other as we would expect.Upvotes: 2
Reputation: 54
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
Upvotes: 2