The issue
I am writing a simple test for a Laravel repository, which is used to get different products from a database. The repository currently works fine, but writing tests is proving very difficult.
Depending on which code I write, there are two different issues.
1) If I instantiate a new Mock of a Class, the method I am trying to call is not recognised.
2) If I reference an existing instance of the Mock that was created in the setUp method of the test, the Mock tries to connect to a database instead of using the mocked method.
The files
My project general setup is as follows:
- Product.php (model)
- DbProductRepository.php (repository)
- DbProductRepositoryTest.php (test)
The code
DbProductRepository.php
// Return all products, where an optionally set 'purchasable' flag is set
public function loadAll($only_purchasable = null)
{
return $this->product
->with('prices.currency')
->where(($only_purchasable ? 'purchasable' : 'cat_id'), ($only_purchasable ? '=' : '>='), ($only_purchasable ? true : '0'))
->orderBy('cat_order', 'asc')
->get();
}
The repository method works fine, but I would like to write a test as an exercise to see how that would be done.
DbProductRepositoryTest.php
use App\Repositories\Product\DbProductRepository;
class DbProductRepositoryTest extends TestCase{
protected $repository;
protected $category_hierarchy;
protected $currency;
protected $product;
protected $request;
protected $cache;
protected $collection;
public function setUp()
{
parent::setUp();
$this->collection = $this->mock('Illuminate\Database\Eloquent\Collection');
$this->category_hierarchy = $this->mock('App\Helpers\CategoryHierarchy');
$this->cache = $this->mock('Illuminate\Cache\Repository');
$this->request = $this->mock('Illuminate\Http\Request');
$this->currency = $this->mock('App\Currency');
$this->product = $this->mock('App\Product');
}
public function tearDown()
{
parent::tearDown();
Mockery::close();
}
public function mock($class, $partial = false)
{
if($partial)
{
$mock = Mockery::mock($class)->makePartial();
}
else
{
$mock = Mockery::mock($class);
}
$this->app->instance($class, $mock);
return $mock;
}
/**
* @test
*/
public function it_returns_all_products()
{
// Given there are 5 products.
$productA = $this->createMockProduct();
$productB = $this->createMockProduct();
$productC = $this->createMockProduct();
$productD = $this->createMockProduct();
$productE = $this->createMockProduct();
// Set up the expected return Collection.
$collection = $this->setupExpectedCollection([$productA, $productB, $productC, $productD, $productE]);
// Mock the model that performs the db call.
$model = $this->product;
$model->shouldReceive('with')->once()->andReturnSelf();
$model->shouldReceive('where')->once()->andReturnSelf();
$model->shouldReceive('orderBy')->once()->andReturnSelf();
$model->shouldReceive('get')->once()->andReturn($collection);
// Instantiate the repository.
$repository = new DbProductRepository(
$this->category_hierarchy,
$this->currency,
$model,
$this->request,
$this->cache
);
// When I request all products.
$products = $repository->loadAll();
// Then a Collection with all the products is returned.
$this->assertCount(5, $products);
}
private function createMockProduct($properties = null, $partial = true)
{
$product = $this->mock('App\Product', $partial);
if(is_array($properties))
{
foreach($properties as $key=>$value)
{
$product->$key = $value;
}
}
return $product;
}
private function setupExpectedCollection(array $data)
{
$collection = new Illuminate\Database\Eloquent\Collection();
foreach($data as $item)
{
$collection->add($item);
}
return $collection;
}
The problem
The mock for the model is not being created properly I believe, as the error message I get when running the unit test is:
1) DbProductRepositoryTest::it_returns_all_products
InvalidArgumentException: Database does not exist.
/var/www/html/laravel/vendor/laravel/framework/src/Illuminate/Database/Connectors/SQLiteConnector.php:34
/var/www/html/laravel/vendor/laravel/framework/src/Illuminate/Database/Connectors/ConnectionFactory.php:58
/var/www/html/laravel/vendor/laravel/framework/src/Illuminate/Database/Connectors/ConnectionFactory.php:47
/var/www/html/laravel/vendor/laravel/framework/src/Illuminate/Database/DatabaseManager.php:177
/var/www/html/laravel/vendor/laravel/framework/src/Illuminate/Database/DatabaseManager.php:65
/var/www/html/laravel/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php:3146
/var/www/html/laravel/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php:3112
/var/www/html/laravel/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php:1887
/var/www/html/laravel/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php:1828
/var/www/html/laravel/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php:1802
/var/www/html/laravel/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php:882
/var/www/html/laravel/app/Product.php:97
/var/www/html/laravel/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php:2638
/var/www/html/laravel/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php:2573
/var/www/html/laravel/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php:3263
/var/www/html/laravel/app/Repositories/Product/DbProductRepository.php:257
/var/www/html/laravel/app/Repositories/Product/DbProductRepository.php:70
/var/www/html/laravel/tests/Repositories/Product/DbProductRepositoryTest.php:81
Line 97 - Product.php
public function prices()
{
return $this->hasMany('App\Price', 'product_id', 'cat_id');
}
However...
I have written another test for a different method in the ProductRepository, where the model is being properly mocked and a connection to the database isn't being attempted:
/**
* @test
*/
public function it_returns_all_categories_that_dispatch_emails()
{
// Given there are three products and two of them dispatch emails.
$productA = $this->createMockProduct(['dispatches_emails'=>true]);
$productB = $this->createMockProduct(['dispatches_emails'=>true]);
$productC = $this->createMockProduct(['dispatches_emails'=>false]);
// Set up the expected return Collection.
$collection = $this->setupExpectedCollection([$productA, $productB]);
// Mock the model that performs the db call.
$model = Mockery::mock('App\Product');
// Set up the model expectations.
$model->shouldReceive('where')->once()->with('dispatches_emails', true)->andReturnSelf();
$model->shouldReceive('get')->once()->andReturn($collection);
// Instantiate the repository.
$repository = new DbProductRepository(
$this->category_hierarchy,
$this->currency,
$model,
$this->request,
$this->cache
);
// When I request the categories which dispatches emails from the repository.
$products = $repository->getDispatchesEmailCategories();
// Then a Collection with two products dispatcher products is returned.
$this->assertEquals($collection, $products);
// And the product which doesn't dispatch e-mails is excluded.
$this->assertNotContains($productC, $products);
}
The related (working) method
public function getDispatchesEmailCategories()
{
return $this->product->where('dispatches_emails', true)->get();
}
Clearly a much simpler query being run by the repository, but I don't see why this test works but the previous doesn't.
One quirk I've come across, is that on the first test - if I change the $model from:
$model = $this->product;
to:
$model = Mockery::mock('App\Product');
Then, the mock appears to not use the Database - but complains with the following error:
BadMethodCallException: Static method Mockery_5_App_Product::with() does not exist on this mock object
Aucun commentaire:
Enregistrer un commentaire