My question when i first started doing unit test was “why do i need unit test when i know my code is working well?”
I will be using PHP language & laravel framework for the demonstration
Unit test is basically the testing of smaller components of an application so that the code has to always work the way is it written in that time. Lets see an example
Lets say we are building an inventory application
namespace App\Http\Controllers;
use App\Inventory;
use Illuminate\Http\Request;
class InventoryController extends Controller
{
private $inventory;
private $request;
public function __construct(Inventory $inventory, Request $request)
{
$this->inventory = $inventory;
$this->request = $request;
}
public function create()
{
if ($this->request->has('name')) {
if ($this->inventory->create($this->request->all())) {
return response(['status' => 'success']);
}
}
return response(['status' => 'error']);
}
}
So what this code simple does is , it takes a request from user validates the request and sends response i.e( success or error).
Lets imagine a scenario “what if in future you need extra fields for inventory item like (name, price, tag etc) ,modify the response standards (status,message,data etc ),or notify admin (send email ) that someone has added an item. What if you were not there so other developers had to take it over? So no one will know the overall implication of code except you (if you have a very good memory to remember all piece of code like a git :) ) Now we are starting to face problems . So what can we do to overcome these problems?
UNIT TEST will be your saviour we can write something like
namespace Tests\Unit\Controllers;
use App\Http\Controllers\InventoryController;
use App\Inventory;
use Illuminate\Http\Request;
use Mockery;
class InventoryControllerTest extends \Tests\TestCase
{
protected $inventoryController;
protected $request;
protected $inventory;
public function setUp()
{
parent::setUp();
$this->inventoryController = Mockery::mock(InventoryController::class)->makePartial();
$this->request = Mockery::mock(Request::class)->makePartial();
$this->inventory = Mockery::mock(Inventory::class)->makePartial();
}
public function makePropertyAccessible(&$object, $property, $value, $class)
{
$reflection = new \ReflectionClass($class);
$property = $reflection->getProperty($property);
$property->setAccessible(true);
$property->setValue($object, $value);
}
public function testIfCreateIsWorkingWhenDetailsAreCorrect()
{
$this->request->shouldReceive('all')->andReturn(['name' => 'desk']);
$this->inventory->shouldReceive('create')->andReturn(true);
$this->makePropertyAccessible($this->inventoryController, 'inventory', $this->inventory, InventoryController::class);
$this->makePropertyAccessible($this->inventoryController, 'request', $this->request, InventoryController::class);
$reponse = json_decode($this->inventoryController->create()->getContent(), true);
$this->assertSame($reponse, ['status' => 'success']);
}
}
Looks like a alien has encrypted something for us let me break down this code for you -:
$this->inventoryController = Mockery::mock(InventoryController::class)->makePartial();
This piece of code makes a replica inventoryController class where you are god & can do anything with the functionality of method (protected & public ) and properties;
method makePropertyAccessible()
this method is generally overriding the desired expectation,outcomes from a method or properties of mocked classes.
public function testIfCreateIsWorkingWhenDetailsAreCorrect()
{
$this->request->shouldReceive('all')->andReturn(['name' => 'desk']);
$this->inventory->shouldReceive('create')->andReturn(true);
$this->makePropertyAccessible($this->inventoryController, 'inventory', $this->inventory, InventoryController::class);
$this->makePropertyAccessible($this->inventoryController, 'request', $this->request, InventoryController::class);
$reponse = json_decode($this->inventoryController->create()->getContent(), true);
$this->assertSame($reponse, ['status' => 'success']);
}
}
Lets break down this method further more
$this->request->shouldReceive('all')->andReturn(['name' => 'desk']);
Whenever the $this->request->all() is called it will return the array [‘name’ => ‘desk’]
$this->inventory->shouldReceive('create')->andReturn(true);v
whenever the $this->inventory->create() is called we wanted to return true because we do not want to connect database assuming when the data after validation it will create a row in table. If you are still in doubt you can use database while testing i prefer sqlite as it is lightweight and fits the CI/CD deployment.
$this->makePropertyAccessible($this->inventoryController, 'inventory', $this->inventory, InventoryController::class);
This is basically overriding the property of inventoryController class with our mocked class so after doing this whenever the create method of inventory model is triggered from the inventoryController it will return true.
$response = json_decode($this->inventoryController->create()->getContent(), true);
$this->assertSame($response, ['status' => 'success']);
This final piece of code is asserting that whenever the creation is successfull it will always return the response in the same format like [‘status’ => ‘success’].
run the test vendor/bin/phpunit in your console. You will see something like this -:
PHPUnit 6.5.11 by Sebastian Bergmann and contributors.
… 3 / 3 (100%)
Time: 311 ms, Memory: 14.00MB
OK (3 tests, 5 assertions)
Ok now after one day we write the method create to this one -:
public function create()
{
if ($this->request->has('name') && $this->request->has('price')) {
if ($this->inventory->create($this->request->all())) {
event(new NotifyAdmin($this->inventory));
return response(['status' => 'success','message' => 'item has been successfully added']);
}
}
return response(['status' => 'error','message' => 'item name or price is missing']);
}
Price & Name attributes are required,Notification Event is triggered & Extra fields are added on response. Whenever we run the test now
There was 1 failure:
1)Tests\Unit\Controllers\InventoryControllerTest::testIfCreateIsWorkingWhenDetailsAreCorrect
Failed asserting that Array &0 (
‘status’ => ‘success’
) is identical to Array &0 (
‘status’ => ‘success’
‘message’ => ‘item has been successfully added’
).
/var/www/tests/Unit/Controllers/InventoryControllerTest.php:44
Now we will cover these changes in our tests
public function testIfCreateIsWorkingWhenDetailsAreCorrect()
{
Event::fake();
$this->request->shouldReceive('all')->andReturn(['name' => 'desk','price' => 2]);
$this->inventory->shouldReceive('create')->andReturn(true);
$this->makePropertyAccessible($this->inventoryController, 'inventory', $this->inventory, InventoryController::class);
$this->makePropertyAccessible($this->inventoryController, 'request', $this->request, InventoryController::class);
$response = json_decode($this->inventoryController->create()->getContent(), true);
Event::assertDispatched(NotifyAdmin::class);
$this->assertSame($response, ['status' => 'success','message' => 'item has been successfully added']);
}
Conclusion
Lets answer to our question “why do i need unit test when i know my code is working well?”
The test we are writing is for the future what if next time you need to modify your response standards, process other things before & after the creation of item in inventory. As a developer we will not know the every implications of changing the code in an application.
So by writing testing you can see the implications of your code in an application that means whenever you are shipping the next module you will easily know which things are going to break out. This kept the track of things if we were to use same components for many classes so changing one could affect the whole other classes and we would instantly know the implications in a minute.
Most of the language has their own testing framework all you need to do is get yourself prepared , google and write some failing tests.
You can see the full code at github.
Top comments (0)