Chúng ta đều biết Mockito là một thư viện vô cùng hữu ích trong Unit Test. Nó là cách tốt nhất để tạo ra các mock (mô phỏng/giả lập) và sử dụng nó trong Java và Kotlin. Nhưng do là một thư viện được xây dựng cho Java, bởi thế nên nó sẽ có những hạn chế nhất định khi sử dụng cho Kotlin. Đó cũng là lý do cho sự ra đời của MockK – một mocking framework được viết hoàn toàn bằng Kotlin và dành riêng cho Kotlin.
Giống như Mockito, MockK cho phép bạn tạo và stub các đối tượng bên trong test code của mình.
Việc mock các object cho phép bạn test trên các đối tượng độc lập. Bất kỳ sự phụ thuộc vào object nào khi test đều có thể được mock để cung cấp các điều kiện cố định, do đó, đảm bảo các test luôn ổn định và rõ ràng.
Mockito là một framework phổ biến được dùng bởi các Java developer và vô cùng mạnh mẽ. Nhưng nó lại có một vài khó chịu khi được áp dụng cho Kotlin. MockK, được thiết kế đặc biệt cho Kotlin, sẽ mang lại cho chúng ta một trải nghiệm thoải mái hơn nhiều.
Đối với Kotlin 1.3 trở lên, ta cần khai báo dependency (Kotlin DSL) cho MockK như sau:
1 2 | <span class="token function">testImplementation</span><span class="token punctuation">(</span><span class="token string">"io.mockk:mockk:1.10.0"</span><span class="token punctuation">)</span> |
Phiên bản MockK mới nhất tính đến thời điểm này là 1.10.0.
Một đoạn mã mẫu là:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <span class="token keyword">class</span> User <span class="token punctuation">{</span> <span class="token keyword">var</span> address<span class="token operator">:</span> Adddress <span class="token keyword">var</span> contact<span class="token operator">:</span> Contact <span class="token punctuation">}</span> <span class="token keyword">class</span> MyTest <span class="token punctuation">{</span> <span class="token annotation builtin">@MockK</span> <span class="token keyword">private</span> <span class="token keyword">lateinit</span> <span class="token keyword">var</span> addressMock<span class="token operator">:</span> Address <span class="token annotation builtin">@MockK</span> <span class="token keyword">private</span> <span class="token keyword">lateinit</span> <span class="token keyword">var</span> contactMock<span class="token operator">:</span> Contact <span class="token annotation builtin">@InjectMockks</span> <span class="token keyword">val</span> user<span class="token operator">:</span> User <span class="token operator">=</span> <span class="token function">User</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token annotation builtin">@Before</span> <span class="token keyword">fun</span> <span class="token function">setUp</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> MockkAnnotations<span class="token punctuation">.</span><span class="token function">init</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> relaxUnitFun <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token annotation builtin">@Test</span> <span class="token keyword">fun</span> <span class="token function">testFunction</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token operator">..</span><span class="token operator">..</span><span class="token operator">..</span> <span class="token operator">..</span><span class="token operator">..</span><span class="token operator">..</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Bây giờ hãy xem thư viện MockK này có gì đặc biệt nhé.
1. Chained mock:
Final result có thể được return bằng cách chỉ định chuỗi lệnh gọi hoàn chỉnh. Không cần phải mô phỏng từng đầu ra của phương thức.
1 2 3 | <span class="token keyword">val</span> product<span class="token operator">:</span> Product <span class="token operator">=</span> <span class="token function">mockk</span><span class="token punctuation">(</span><span class="token punctuation">)</span> every <span class="token punctuation">{</span> product<span class="token punctuation">.</span>currentOffer<span class="token punctuation">.</span>isAcive <span class="token punctuation">}</span> returns <span class="token boolean">true</span> |
Giả sử phiếu mua hàng có nhiều loại khác nhau và kiểu return của nó là generic thì hint
sẽ giúp đưa ra gợi ý để mock đúng kiểu mà chúng ta cần.
1 2 3 4 5 | <span class="token keyword">val</span> product<span class="token operator">:</span> Product <span class="token operator">=</span> <span class="token function">mockk</span><span class="token punctuation">(</span><span class="token punctuation">)</span> every <span class="token punctuation">{</span> product<span class="token punctuation">.</span>currentOffer<span class="token punctuation">.</span><span class="token function">hint</span><span class="token punctuation">(</span>Offer<span class="token punctuation">.</span>SpecialOffer<span class="token operator">::</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">.</span>isAcive <span class="token punctuation">}</span> returns <span class="token boolean">true</span> |
2. Đối tượng Mock:
Một điều rất thú vị về MockK đó các object cũng có thể được mock.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <span class="token keyword">object</span> Calculator <span class="token punctuation">{</span> <span class="token keyword">fun</span> <span class="token function">add</span><span class="token punctuation">(</span>a<span class="token operator">:</span> Int<span class="token punctuation">,</span> b<span class="token operator">:</span> Int<span class="token punctuation">)</span><span class="token operator">:</span> Int <span class="token punctuation">{</span> <span class="token operator">..</span><span class="token punctuation">.</span> <span class="token punctuation">}</span> <span class="token operator">..</span><span class="token operator">..</span><span class="token punctuation">.</span> <span class="token operator">..</span><span class="token operator">..</span><span class="token punctuation">.</span> <span class="token punctuation">}</span> <span class="token annotation builtin">@Test</span> <span class="token keyword">fun</span> <span class="token function">testMethod</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">mockkObject</span><span class="token punctuation">(</span>Calculator<span class="token punctuation">)</span> every <span class="token punctuation">{</span> Calculator<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token function">any</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">any</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> returns <span class="token number">0</span> <span class="token function">assertEquals</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> Calculator<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token function">unmockkAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// Hoặc unmockkObject(Calculator)</span> <span class="token punctuation">}</span> |
Một điều thú vị khi mock một object đó là mặc dù object chỉ có một thể hiện (instance) duy nhất nhưng mockk<Calculator>()
lại có thể có nhiều thể hiện khác nhau. Thậm chí các enum cũng có thể được mock theo cách này.
3. Capturing
Các giá trị/thuộc tính của đối tượng có thể được capture bằng cách sử dụng slot
hoặc mutableList
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <span class="token keyword">val</span> product<span class="token operator">:</span> Product <span class="token operator">=</span> <span class="token function">mockk</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">val</span> slot <span class="token operator">=</span> Slot<span class="token operator"><</span>Date<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span> every <span class="token punctuation">{</span> product<span class="token punctuation">.</span><span class="token function">getCommentsPostedBy</span><span class="token punctuation">(</span><span class="token function">capture</span><span class="token punctuation">(</span>slot<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> answers <span class="token punctuation">{</span> <span class="token function">println</span><span class="token punctuation">(</span>slot<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">val</span> today<span class="token operator">:</span> Date <span class="token operator">=</span> <span class="token function">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">val</span> calendar <span class="token operator">=</span> Calendar<span class="token punctuation">.</span><span class="token function">getInstance</span><span class="token punctuation">(</span><span class="token punctuation">)</span> calendar<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>Calendar<span class="token punctuation">.</span>DAY_OF_YEAR<span class="token punctuation">,</span> <span class="token operator">-</span>daysAgo<span class="token punctuation">)</span> <span class="token keyword">val</span> yesterday <span class="token operator">=</span> calendar<span class="token punctuation">.</span>time <span class="token function">verify</span><span class="token punctuation">(</span>exactly <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> product<span class="token punctuation">.</span><span class="token function">getCommentsPostedBy</span><span class="token punctuation">(</span><span class="token function">or</span><span class="token punctuation">(</span>today<span class="token punctuation">,</span> yesterday<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> |
4. Mocking cả suspend function:
Mockk cũng hỗ trợ mock các hàm suspend.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <span class="token comment">// Repository.kt</span> <span class="token keyword">suspend</span> <span class="token keyword">fun</span> <span class="token function">getDataFromServer</span><span class="token punctuation">(</span> productId<span class="token operator">:</span> String <span class="token punctuation">)</span><span class="token operator">:</span> ServerData <span class="token operator">=</span> <span class="token function">withContext</span><span class="token punctuation">(</span>Dispatchers<span class="token punctuation">.</span>IO<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token operator">..</span><span class="token operator">..</span><span class="token operator">..</span> <span class="token punctuation">}</span> <span class="token comment">// Test.kt</span> <span class="token annotation builtin">@Test</span> <span class="token keyword">fun</span> <span class="token function">testData</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">val</span> repository<span class="token operator">:</span> Repository <span class="token operator">=</span> <span class="token function">mockk</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">val</span> serverData<span class="token operator">:</span> ServerData <span class="token operator">=</span> <span class="token function">ServerData</span><span class="token punctuation">(</span><span class="token punctuation">)</span> coEvery <span class="token punctuation">{</span> respository<span class="token punctuation">.</span><span class="token function">getDataFromServer</span><span class="token punctuation">(</span><span class="token string">"p1"</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> retuns serverData <span class="token punctuation">}</span> |
5. Mocking cả Extension function
Mocking extension function.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <span class="token keyword">class</span> Product <span class="token punctuation">{</span> <span class="token operator">..</span><span class="token operator">..</span><span class="token punctuation">.</span> <span class="token punctuation">}</span> <span class="token keyword">class</span> Experiment <span class="token punctuation">{</span> <span class="token keyword">fun</span> Product<span class="token punctuation">.</span><span class="token function">newFunc</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> Int <span class="token punctuation">{</span> <span class="token operator">..</span><span class="token operator">..</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token comment">// Test</span> <span class="token keyword">val</span> experimentMock <span class="token operator">=</span> mockk<span class="token operator"><</span>Experiment<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token function">with</span><span class="token punctuation">(</span>experimentMock<span class="token punctuation">)</span> <span class="token punctuation">{</span> every <span class="token punctuation">{</span> <span class="token function">Product</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">newFunc</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> returns <span class="token number">5</span> <span class="token function">assertEuals</span><span class="token punctuation">(</span><span class="token number">5</span><span class="token punctuation">,</span> <span class="token function">Product</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">newFunc</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> verify <span class="token punctuation">{</span> <span class="token function">Product</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">newFunc</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
6. Mocking mô-đun:
Vì Kotlin cho phép viết các hàm trực tiếp trong tệp mà không cần khai báo lớp nào, nên trong những trường hợp như vậy, chúng ta cần phải mock toàn bộ tệp với mockStatic
.
1 2 3 4 5 6 7 8 9 10 11 12 | <span class="token keyword">class</span> Product <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token comment">// package.File.kt</span> <span class="token keyword">fun</span> Product<span class="token punctuation">.</span><span class="token function">newFunc</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token operator">..</span><span class="token operator">..</span><span class="token punctuation">.</span> <span class="token punctuation">}</span> <span class="token comment">// Test</span> <span class="token function">mockStatic</span><span class="token punctuation">(</span><span class="token string">"package.FileKt"</span><span class="token punctuation">)</span> every <span class="token punctuation">{</span> <span class="token function">Product</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">newFunc</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> returns <span class="token number">5</span> <span class="token function">assertEquals</span><span class="token punctuation">(</span><span class="token number">5</span><span class="token punctuation">,</span> <span class="token function">Product</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">newFunc</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token function">verify</span><span class="token punctuation">(</span><span class="token function">Product</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">newFunc</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> |
7. Mock private function:
MockK giúp cho việc mock các private method rất dễ dàng.
1 2 3 4 5 6 7 8 9 | <span class="token keyword">class</span> Product <span class="token punctuation">{</span> <span class="token keyword">fun</span> <span class="token function">firstFunc</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span>Int <span class="token operator">=</span> <span class="token function">secondFunc</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">fun</span> <span class="token function">secondFunc</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> Int <span class="token operator">=</span> <span class="token number">5</span> <span class="token punctuation">}</span> <span class="token comment">// Test</span> <span class="token keyword">val</span> product <span class="token operator">=</span> spyk<span class="token operator"><</span>Product<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span> every <span class="token punctuation">{</span> product<span class="token punctuation">[</span><span class="token string">"secondFunc"</span><span class="token punctuation">]</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> returns <span class="token number">10</span> <span class="token function">assertTrue</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">,</span> product<span class="token punctuation">.</span><span class="token function">firstFunc</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> |
Nếu bạn muốn verify các lần gọi tới các phương thức private thì bạn cần phải set recordPrivateCalls=true
trong khi tạo spy
.
1 2 3 4 5 6 7 8 | <span class="token keyword">val</span> product <span class="token operator">=</span> spyk<span class="token operator"><</span>Product<span class="token operator">></span><span class="token punctuation">(</span>recordPrivateCalls <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">)</span> every <span class="token punctuation">{</span> product<span class="token punctuation">[</span><span class="token string">"secondFunc"</span><span class="token punctuation">]</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> returns <span class="token number">10</span> <span class="token function">assertTrue</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">,</span> product<span class="token punctuation">.</span><span class="token function">firstFunc</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> verifySequence <span class="token punctuation">{</span> product<span class="token punctuation">.</span><span class="token function">firstFunc</span><span class="token punctuation">(</span><span class="token punctuation">)</span> product<span class="token punctuation">[</span><span class="token string">"secondFunc"</span><span class="token punctuation">]</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> |
8. Ngay cả constructor cũng có thể được mock:
Việc unit test sẽ trở nên rất nản nếu mã gốc tạo ra các đối tượng của một lớp và sau đó thực hiện các lệnh gọi hàm trong hàm mục tiêu kiểm tra. Nó có thể được giải quyết bằng cách sử dụng mock constructor.
1 2 3 4 5 6 7 8 9 10 | <span class="token keyword">class</span> Product <span class="token punctuation">{</span> <span class="token keyword">fun</span> <span class="token function">getNewClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> Int <span class="token operator">=</span> <span class="token function">NewClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token comment">// Test</span> <span class="token keyword">val</span> product <span class="token operator">=</span> spyk<span class="token operator"><</span>Product<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token function">mockkContructor</span><span class="token punctuation">(</span>NewClass<span class="token operator">::</span><span class="token keyword">class</span><span class="token punctuation">)</span> every <span class="token punctuation">{</span> anyContructed<span class="token operator"><</span>NewClass<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> returns <span class="token number">5</span> <span class="token function">assertTrue</span><span class="token punctuation">(</span><span class="token number">5</span><span class="token punctuation">,</span> product<span class="token punctuation">.</span><span class="token function">getNewClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> verify <span class="token punctuation">{</span> anyConstructed<span class="token operator"><</span>NewClass<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> |
Nguồn: https://medium.com/@prashantspol/mockk-better-to-way-to-mock-in-kotlin-than-mockito-1b659c5232ec