Certainly developers who wrote unit tests used to more or less use mock objects. However, some people still do not understand why they have to do mocking but only use it roughly “because everyone else does”. Here we will explore some of the reasons for mocking an object and common ways to mock in Laravel.
TABLE OF CONTENTS
- What is Mocking?
- Why Mocking in the first place?
- What is Mockery?
- How to use Mockery?
I. What is Mocking?
Mocking is an approach to unit testing that uses mock objects to perform mock interaction between objects in another module. In mock testing, external dependent objects are replaced with a simulation with the same behavior as the real object.
II. Why Mocking in the first place?
An object being tested can have many dependencies. For ease of testing we should replace dependent objects with mock objects. Mocking an object is not redundant, but it also brings a lot of benefits:
- Ensure isolation during test: Since it is not practical to call the dependent object methods, database and file system interactions will not be performed, and therefore will not affect the kiểm TRA. The input and output values will be determined for each method call. Furthermore, this also ensures that the testing process is independent between the unit under test and its dependencies. If the dependency is wrong, but the unit is testing correctly, the result will still be correct.
- Easily simulate situations: Instead of having to cleverly compute the input to cover all test cases, by replacing dependencies outside of the test scope, we can clearly define the values. input, output as expected. Thereby, we will easily cover all situations, even those unlikely to happen.
- Speed up testing: Since test functions do not actually query or compute against out-of-scope objects, testing for each test case is much faster than without mocking. objects.
III. What is Mockery?
Mockery is a simple and easy-to-use PHP test framework to mock objects in unit tests. Mock objects are a simulation of a real object. The mock function will simulate input, output, etc. in accordance with the expectations of the execution of the mocked class’s functions / behaviors.
IV. How to use Mockery?
System requirements
- Mockery requires at least PHP 5.6.
Setting
For Laravel version 5.4 and above, Mockery is included with the framework installation. However, if needed we can also install for lower versions. To install the mockery we can use composer to run the command:
1 2 | composer require --dev mockery/mockery. |
The basics of how to use Mockery
a. Create mock objects using Mockery
To create mock objects we use the mock('MyClass')
method mock('MyClass')
. Mockery allows us to mock an entire class or just a few functions using the makePartial()
. For example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | namespace TestsUnit; use AppClassToMock; use ControllerToTest; use TestsTestCase; use Mockery; class ControllerTestCase extends TestCase { protected $controller, $mockObject; public function setUp() : void { parent::setUp(); $this->mockObject = Mockery::mock(ClassToMock::class); /* hoặc $this->mockObject = Mockery::mock('AppClassToMock'); để giả lập một vài method $this->mockObject = Mockery::mock(ClassToMock::class)->makePartial(); $this->mockObject = Mockery::mock('AppClassToMock[method]'); */ $this->controller = new ControllerToTest($this->mockObject); // Other setting up ... } public function tearDown() { // Other tearing down ... Mockery::close(); unset($this->controller); parent::tearDown(); } // Testing functions... } |
b. Options
For the most rigorous testing possible, we should add the options Mockery provides. Here, I would like to introduce some of the most commonly used options in the testing process. Usually when making an object mock we need to define:
- Function name should be mock
- Input parameters
- Results returned
- Number of runs
Function name should be mock
To define the function that needs mock we can declare when initializing the Mockery::mock('AppClassToMock[method_1, method_2]')
method or using the shouldReceive('method')
. For example:
1 2 3 4 | $this->mockObject = Mockery::mock(ClassToMock::class)->makePartial(); $this->mockObject ->shouldReceive('method'); |
Input parameters
By default, Mockery will initialize the functions with the withAnyArgs()
option. With this option, Mockery will allow any input value. However, if we want a stricter testcase we can define the input using the options:
1 2 3 4 5 6 7 8 9 10 | // không có tham số đầu vào $this->mockObject ->shouldReceive('method') ->withNoArgs(); // với các tham số đầu vào cụ thể $this->mockObject ->shouldReceive('method') ->with($arg_1, $arg_2); // hoặc withArgs([$arg_1, $arg_2]); |
Results returned
In case our function returns, we can use the andReturn()
, andReturnNull()
to return the result, or andThrow()
to throw an exception after calling the function. For example:
1 2 3 4 5 6 7 8 9 10 11 12 | // không có tham số đầu vào $this->mockObject ->shouldReceive('method') ->withNoArgs() ->andReturn($response); // với các tham số đầu vào cụ thể $exception = new Exception(); $this->mockObject ->shouldReceive('method') ->with($arg_1, $arg_2) ->andThrow($exception); |
Number of runs
We can determine the number of times the method is run using the once()
, twice()
, times($number_of_times)
or never()
methods. For example:
1 2 3 4 5 6 7 | // Hàm 'method' sẽ được gọi 2 lần với đầu vào là $arg và ném ra ngoại lệ $exception. $this->mockObject ->shouldReceive('method') ->with($arg) ->andThrow($exception) ->twice(); |
Several other options
In addition to the options mentioned above. Mockery also includes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // cho phép mock các hàm protected. shouldAllowMockingProtectedMethods() // cho phép loại phương thức khỏi mock object. shouldNotReceive('name_of_method'); // trả về nhiều giá trị khác nhau. andReturnValues([$value_1, $value_2]); // trả về giá trị theo closure andReturnUsing(closure, ...); // trả về chính nó andReturnSelf(); // đặt giá trị thuộc tính andSet($property, $value); // đặt số lần gọi tối đa, tối thiểu atMost($max); atLeast($min); between($min, $max); |
c. Mock the Facades in Laravel.
Mock Event, Mail, Notification, Queue, Storage.
To mock these facades use the fake()
. After the call, the function replaces the current instance into a mock object during the test. For example:
1 2 | Queue::fake(); |
Laravel also provides assertion support for testing fake facades such as:
1 2 3 | // Event fake Event::assertDispatched(); |
1 2 3 4 | // Mail fake Mail::assertSent(); Mail::assertNotSent(); |
1 2 3 4 | // Notification fake Notification::assertSentTo(); Notification::assertNotSentTo(); |
1 2 3 4 5 | // Queue fake Queue::assertPushed(); Queue::assertPushedOn(); Queue::assertNotPushed(); |
1 2 3 4 | // Storage fake Storage::disk('disk_name')->assertExists(); Storage::disk('disk_name')->assertMissing(); |
Other Facades
As with most of the other Facades in Laravel, it is easy to mock them during testing. For example:
1 2 3 4 5 | Cache::shouldReceive('get') ->once() ->with('key') ->andReturn($value); |
SUMMARY
In the last article we went into understanding mocking, the benefits of using mock objects, how to mock an object using Mockery in Laravel. What is missing article, please comment.