Unit Test là một công cụ giúp cho chúng ta có thể đảm bảo đoạn code mà chúng ta viết ra đúng chính xác những gì chúng ta muốn. Trong quá trình làm việc với Unit Test, một vấn đề mà chúng ta thường gặp đó là code của chúng ta sẽ gọi tới những phương thức của các class khác, bên ngoài class, phương thức mà chúng ta đang viết. Chúng ta có thể khởi tạo những class bên ngoài đó để gọi phương thức mà chúng ta muốn, nhưng đâu ai biết được là các phương thức của những class bên ngoài đó lại gọi những phương thức của các class bên ngoài nữa và câu hỏi đặt ra là có cần thiết phải quan tâm đến những class bên ngoài đó khi chúng ta đang test phương thức của class này bởi vì những class bên ngoài đó cũng sẽ có Unit Test riêng.
Câu trả lời theo mình là không các bạn? Unit Test là chúng ta test những đoạn code nhỏ, sẽ có Unit Test để đảm bảo logic cho những external dependencies, việc chúng ta chỉ là đảm bảo cho code hiện tại chúng ta đã test mà thôi. Trong trường hợp này, một vấn đề đặt ra là nếu phương thức chúng ta đang gọi tới phương thức của class bên ngoài thì sẽ xử lý như thế nào, làm thế nào để khi code chúng ta gọi đến những phương thức đó, kết quả trả về sẽ giúp chúng ta cover được logic trong Unit Test?
Để giải quyết vấn đề này, trong Unit Test, người ta có một khái niệm gọi là Mock, dùng để giả lập hành vi của các class bên ngoài để thực hiện hành vi mà chúng ta mong muốn. Khi đó, những đoạn code gọi các phương thức bên ngoài sẽ trả về giá trị mà chúng ta mong muốn. Bởi vì code do chúng ta control nên việc giả lập này vẫn đảm bảo quality của những đoạn code mà chúng ta đang viết Unit Test. Trong bài viết này, mình sẽ hướng dẫn các bạn cách sử dụng Mock trong Unit Test với một thư viện Mocking phổ biến trong Java đó là Mockito, các bạn nhé!
Đầu tiên, mình sẽ tạo mới một Maven project để làm ví dụ:
với Mockito và JUnit dependencies như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-junit-jupiter</artifactId> <version>3.6.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>5.7.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.7.0</version> <scope>test</scope> </dependency> |
Với JUnit 5, để chạy được Unit Test với Maven, chúng ta cần khai báo plugin maven-surefire-plugin của Maven với latest version như sau:
1 2 3 4 5 6 7 8 |
<build> <plugins> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.22.2</version> </plugin> </plugins> </build> |
Và compile source với target sử dụng Java 8 trở đi:
1 2 3 4 |
<properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> |
Bây giờ mình sẽ tạo một ứng dụng nhỏ, cho phép người dùng truyền vào 2 số, chúng ta sẽ tính tổng 2 số đó, từ kết quả này sẽ trả về true nếu kết quả lớn hơn 10, ngược lại sẽ là false. Cụ thể, code sẽ như sau:
Chúng ta có một class để tính tổng 2 số:
1 2 3 4 5 6 7 8 9 |
package com.huongdanjava.mockito; public class Calculation { public int sum(int a, int b) { return a + b; } } |
Một class chính của ứng dụng trong đó nó có một phương thức để kiểm tra kết quả việc tính tổng và trả về true, false dựa vào kết quả đó:
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 |
package com.huongdanjava.mockito; public class Application { private Calculation calculation; public boolean check(int a, int b) { int sum = calculation.sum(a, b); return sum > 10; } public Calculation getCalculation() { return calculation; } public void setCalculation(Calculation calculation) { this.calculation = calculation; } public static void main(String[] args) { Application a = new Application(); a.setCalculation(new Calculation()); if (a.check(2, 9)) { System.out.println("Greater than 10"); } else { System.out.println("Less than 10"); } } } |
Kết quả khi chạy như sau:
Bây giờ, chúng ta sẽ viết Unit Test cho những class trên các bạn nhé.
Mình sẽ tạo class CalculationTest để test cho class Calculation với nội dung như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
package com.huongdanjava.mockito; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; public class CalculationTest { @Test public void testSum() { Calculation calculation = new Calculation(); int a = 2; int b = 12; int c = calculation.sum(a, b); assertEquals(14, c); } } |
Unit Test cho class Calculation đơn giản phải không các bạn?
Đến Unit Test cho class Application, phương thức main() là phương thức để chạy ứng dụng nên chúng ta không cần test. Chỉ có phương thức check() chúng ta mới cần test mà thôi.
Bình thường, mình có thể viết Unit Test cho phương thức check() này như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
package com.huongdanjava.mockito; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; public class ApplicationTest { @Test public void testCheck() { Application application = new Application(); application.setCalculation(new Calculation()); int a = 2; int b = 12; boolean c = application.check(a, b); assertEquals(true, c); } } |
Thế nhưng nếu xem xét kỹ, các bạn sẽ thấy đoạn code sau trong phương thức check() của class Application:
1 |
int sum = calculation.sum(a, b); |
chúng ta đã test trong class CalculationTest ở trên, nên sẽ không cần phải test nữa. Cái chúng ta chỉ cần test trong phương thức check() này chỉ là đoạn code return mà thôi.
Bây giờ, chúng ta thử apply Mock cho class Calculation xem sao nhé các bạn.
Đầu tiên, các bạn cần sử dụng Mockito để tạo ra Mock object cho class Calculation trước:
1 |
Calculation calculationMock = Mockito.mock(Calculation.class); |
Như các bạn thấy, ở đây, mình đã dùng phương thức static mock() của class Mockito trong thư viện Mockito để tạo ra Mock object cho class Calculation. Có nhiều phương thức static overload mock() trong class Mockito, tuỳ nhu cầu các bạn có thể sử dụng nhé!
Sau khi đã có Mock object của class Calculation thì các bạn có thể set mock object này để sử dụng trong class Application như sau:
1 2 3 4 |
Calculation calculationMock = Mockito.mock(Calculation.class); Application application = new Application(); application.setCalculation(calculationMock); |
Bây giờ thì chúng ta sẽ giả lập hành vi của đối tượng Calculation trong class Application bằng cách sử dụng đối tượng Mock của nó.
Giả sử bây giờ, mình muốn khi class Application gọi đến phương thức sum() của 2 số 2 và 12 trong class Calculation, mình sẽ trả về là 14 mà không cần quan tâm đến logic của phương thức sum() thì mình sẽ mock như sau:
1 |
Mockito.when(calculationMock.sum(2, 12)).thenReturn(14); |
Các bạn có thể return lại một giá trị bất kỳ nhưng cần đảm bảo nó đúng expectation của mình nhé!
Toàn bộ code testCheck() lúc này sẽ như sau:
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 |
package com.huongdanjava.mockito; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; import org.mockito.Mockito; public class ApplicationTest { @Test public void testCheck() { Calculation calculationMock = Mockito.mock(Calculation.class); Application application = new Application(); application.setCalculation(calculationMock); Mockito.when(calculationMock.sum(2, 12)).thenReturn(14); int a = 2; int b = 12; boolean c = application.check(a, b); assertEquals(true, c); } } |
Kết quả:
Nếu bây giờ bạn sửa đoạn code mock phương thức sum() để trả về là 8:
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 |
package com.huongdanjava.mockito; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; import org.mockito.Mockito; public class ApplicationTest { @Test public void testCheck() { Calculation calculationMock = Mockito.mock(Calculation.class); Application application = new Application(); application.setCalculation(calculationMock); Mockito.when(calculationMock.sum(2, 12)).thenReturn(8); int a = 2; int b = 12; boolean c = application.check(a, b); assertEquals(true, c); } } |
thì kết quả sẽ như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
------------------------------------------------------- T E S T S ------------------------------------------------------- Running com.huongdanjava.mockito.ApplicationTest Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.633 sec <<< FAILURE! com.huongdanjava.mockito.ApplicationTest.testCheck() Time elapsed: 0.633 sec <<< FAILURE! org.opentest4j.AssertionFailedError: expected: <true> but was: <false> at org.junit.jupiter.api.AssertionUtils.fail(AssertionUtils.java:56) at org.junit.jupiter.api.AssertEquals.failNotEqual(AssertEquals.java:197) at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:186) at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:181) at org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:500) at com.huongdanjava.mockito.ApplicationTest.testCheck(ApplicationTest.java:25) Running com.huongdanjava.mockito.CalculationTest Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0 sec Results : Failed tests: com.huongdanjava.mockito.ApplicationTest.testCheck(): expected: <true> but was: <false> Tests run: 2, Failures: 1, Errors: 0, Skipped: 0 |
Như các bạn thấy, sử dụng Mock trong Unit Test giúp chúng ta có thể quyết định kết quả trả về của những class bên ngoài mà không cần quan tâm đến việc xử lý của chúng. Chỉ cần đảm bảo cho code mà chúng ta đang Unit Test là được rồi phải không các bạn? 🙂
Lanh
tìm mãi mới có 1 bài viết nói rõ về mock mà dễ hiểu như này
. thanks chủ thớt nhé.
Thao
Cảm ơn bạn, bài viết on point ^^, lấy ví dụ cũng dễ hiểu nữa.
huy nguyen
rất dể hiểu và súc tính :v