I am trying to wrap my head around Unit testing with PhpUnit / Mockery / Laravel. It's not coming easy. I've been reading dozens of tutorials and still can't apply it in real life scenarios.
I will present a piece of code I would like to test. Can anyone please point me on how to test the method modifyBasedOnItemCode() of the class SoldProductModifier?
Few words of explanation first: I want users to be able to type in the product code (item code) together with quantity, and I want the system to automatically update the product_id as well category_id properties for the SoldProduct model. For this purpose I created the class I now would like to test.
Please also see: simplified diagram for my database (only tables related to my question)
Now relevant code:
Class to be tested
use App\Models\Product; class SoldProductModifier { private $sold_product; public function __construct(SoldProduct $sold_product) { $this->sold_product = $sold_product; } public function modifyBasedOnItemCode($item_code) { if (! isset($item_code) || $item_code == '') { $product = Product::findByItemCode($item_code); if (isset($product) && $product != false) { $this->sold_product->category_id = $product->category->id; $this->sold_product->product_id = $product->id; } } return $this->sold_product; } }
Product Model
... public static function findByItemCode($item_code) { return self::where('item_code', $item_code)->first(); } ...
My controller referencing SUT
... $sold_product = new SoldProduct($request->all()); $modifier = new SoldProductModifier($sold_product); $sold_product = $modifier->modifyBasedOnItemCode($request->item_code); $sold_product->save(); ...
My test class
class SoldProductModifierTest extends TestCase { public function setUp() { parent::setUp(); $this->soldProductMock = $this->mock('App\Models\SoldProduct'); $this->productMock = $this->mock('App\Models\Product'); } public function tearDown() { Mockery::close(); } public function testDoesNotModifyIfItemCodeEmpty() { $soldProductModifier = new SoldProductModifier($this->soldProductMock); $modifiedSoldProduct = $soldProductModifier->modifyBasedOnItemCode(''); $this->assertEquals($this->soldProductMock, $modifiedSoldProduct); } public function testModifiesBasedOnItemCode() { // how do I test positive case scenario ? ...
I pasted my first test in case someone thinks it isn't the way it should be done and would be kind to suggest another way of approaching this.
But now to my question:
How do I mock out the call to database here: Product::findByItemCode($item_code) ?
Should I create a $product property in my SoldProductModifier and set it using a setter method created for this purpose, like:
public function setProduct(Product $product) { $this->product = $product; }
and then add extra line in my controller:
... $modifier = new SoldProductModifier($sold_product); $modifier->setProduct(Product::findByItemCode($item_code)); // --- extra line $sold_product = $modifier->modifyBasedOnItemCode(); // --- parameter removed ...
?
I try to keep my controllers as slim as possible, so wanted to avoid that? So what is the best way to tackle this kind of situation?
Thank you