Mock
và Stub
là nền tảng cho việc unit test
nhanh và đơn giản. Mock
hữu ích trong trường hợp bạn có sự phụ thuộc vào hệ thống bên ngoài, việc đọc file tốn nhiều thời gian, kết nối database không đáng tin cậy, hoặc gởi email sau mỗi lần test…
Không giống như integration
hay funtional
test, tức khi mà toàn bộ hệ thống được kiểm thử, unit test
lại tập trung vào một single class
. Mọi thứ khác nên là một simple class hoặc mock. Trong bài viết này, chúng ta sẽ focus vào unit test sử dụng Mockito2
– một mocking framework thống trị trong testing với Java.
Vị trí của Unit Test trong Testing Pyramid
Nó nằm ở dưới cùng của tháp kiểm thử (Testing Pyramid
).
- Kiểm thử 1 single class
- Chỉ cần source code của ứng dụng mà không cần 1 bản build cụ thể.
- Nhanh
- Không bị ảnh hưởng bởi các hệ thống bên ngoài, vd: web service, database …
- Thực hiện ít hoặc không có I/O, vd: không có kết nối database thực sự…
Những test này là thành phần chính của toàn bộ test suite, và bao gồm số lượng lớn trong toàn bộ test của bạn. Đa số có sự nhầm lẫn giữa unit test và integration test, service test, system test hoặc functional test.
Sự phân biệt này rất quan trọng, một test case như write database hay read JSON từ web service thì không phải là một unit test. Nó có thể trở thành unit test nếu bạn mock database hay external web service đó. Unit test và integration test nên được xử lý khác nhau.
Sự cần thiết của mock và stub
Mocking là hành động loại bỏ sự phụ thuộc bên ngoài khỏi unit test để tạo được môi trường kiểm soát được xung quanh nó. Thông thường, chúng ta mock tất cả các class khác tương tác với lớp được test. Các thành phần phổ biến thường nên được mock:
- Database connection
- Web services
- Class mà thực thi chậm
- Class có side effect
- Class với các hành vi không xác định
Mock và stub là các fake Java class
thay thế các thành phần phụ thuộc bên ngoài như nêu trên. Các fake class này sẽ được hướng dẫn (instruction) trước khi chúng có thể hoạt động theo ý của bạn.
- Stub là một fake class kèm theo các giá trị trả về theo các hành động đã được lập trình sẵn theo cách mà bạn hướng đến. Nó sẽ được inject vào class đang cần test để cung cấp cho bạn quyền kiểm soát tuyệt đối với những gì cần test như là đầu vào. Ví dụ: Stub có thể là một database connection cho phép bạn bắt chước bất kì kịch bản nào mà không cần một database thực sự.
- Mock là một fake class có thể được kiểm tra sau khi kết thúc một bài test về sự tương tác của lớp đang được test. Ví dụ: Bạn có thể
verify
một phương thức của nó thực thi hay không và được thực thi bao nhiêu lần. Điền hình của mock là các class có side effect cần được kiểm tra, như class gởi email hoặc gởi dữ liệu đến một dịch vụ bên ngoài.
Basic Stubbing với Mockito
Giả sử chúng ta có một vài class như bên dưới
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <span class="token comment">// Entity class</span> <span class="token annotation punctuation">@Entity</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Customer</span> <span class="token punctuation">{</span> <span class="token annotation punctuation">@Id</span> <span class="token annotation punctuation">@GeneratedValue</span><span class="token punctuation">(</span>strategy <span class="token operator">=</span> GenerationType<span class="token punctuation">.</span>AUTO<span class="token punctuation">)</span> <span class="token keyword">private</span> <span class="token keyword">long</span> id<span class="token punctuation">;</span> <span class="token keyword">private</span> String firstName<span class="token punctuation">;</span> <span class="token keyword">private</span> String lastName<span class="token punctuation">;</span> <span class="token comment">//...getters and setters redacted for brevity...</span> <span class="token punctuation">}</span> <span class="token comment">// Business class</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CustomerReader</span> <span class="token punctuation">{</span> <span class="token annotation punctuation">@PersistenceContext</span> <span class="token keyword">private</span> EntityManager entityManager<span class="token punctuation">;</span> <span class="token keyword">public</span> String <span class="token function">findFullName</span><span class="token punctuation">(</span>Long customerID<span class="token punctuation">)</span><span class="token punctuation">{</span> Customer customer <span class="token operator">=</span> entityManager<span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span>Customer<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">,</span> customerID<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> customer<span class="token punctuation">.</span><span class="token function">getFirstName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span><span class="token string">" "</span><span class="token operator">+</span>customer<span class="token punctuation">.</span><span class="token function">getLastName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">//... other stucks</span> <span class="token punctuation">}</span> |
CustomerReader
class đọc dữ liệu customer từ database thông qua EntityManager
, làm thế nào để viết test cho class này?
Một giải pháp ngây ngô là điền trước một database thực sự với vài dữ liệu customer và tiến hành test. Tuy nhiên, việc này có rất nhiều vấn đề. Thứ nhất, nó tạo ra sự phụ thuộc cứng vào database đang chạy. Thứ hai, bạn cần thêm bước tạo dữ liệu test. Trong ví dụ này, nó có thể hoạt động, như trong thực tế không nên chút nào.
Giải pháp tốt nhất cho một unit test thực sự là loại bỏ hoàn toàn sự phụ thuộc vào database. Chúng ta sẽ Stub
một database connection và hướng (lừa) class rằng nó đang thực thi với một EntityManager
thực sự, trong khi EntityManager
chỉ là một Mockito Stub. Bằng cách này, chúng ta có toàn quyền kiểm soát những gì được thực thi hoặc trả về bởi database connection mà không phải xử lý một database thực tế.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CustomerReaderTest</span> <span class="token punctuation">{</span> <span class="token annotation punctuation">@Test</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">happyPathScenario</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span> Customer sampleCustomer <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Customer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 1</span> sampleCustomer<span class="token punctuation">.</span><span class="token function">setFirstName</span><span class="token punctuation">(</span><span class="token string">"Susan"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> sampleCustomer<span class="token punctuation">.</span><span class="token function">setLastName</span><span class="token punctuation">(</span><span class="token string">"Ivanova"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> EntityManager entityManager <span class="token operator">=</span> <span class="token function">mock</span><span class="token punctuation">(</span>EntityManager<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 2 </span> <span class="token function">when</span><span class="token punctuation">(</span>entityManager<span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span>Customer<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">,</span> <span class="token number">1</span>L<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">thenReturn</span><span class="token punctuation">(</span>sampleCustomer<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 3 - important LOC</span> CustomerReader customerReader <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">CustomerReader</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> customerReader<span class="token punctuation">.</span><span class="token function">setEntityManager</span><span class="token punctuation">(</span>entityManager<span class="token punctuation">)</span><span class="token punctuation">;</span> String fullName <span class="token operator">=</span> customerReader<span class="token punctuation">.</span><span class="token function">findFullName</span><span class="token punctuation">(</span><span class="token number">1</span>L<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 4</span> <span class="token function">assertEquals</span><span class="token punctuation">(</span><span class="token string">"Susan Ivanova"</span><span class="token punctuation">,</span> fullName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Các bước ở trên:
- Tạo 1 sample customer, chúng ta sử dụng real class vì nó đơn giản là một POJO class, không cần phải mock nó.
- Tiếp đó, mock đối tượng
EntityManager
bằng cách gọimock()
function (có thể sử dụng annotation@Mock
) - Bước cực kì quan trọng trong line code tiếp theo. Nó định nghĩa (hướng) điều gì sẽ xảy ra khi gọi hàm
find()
từentityManager
. Tại đây, chúng ta setup rằng sample customer được tạo ở bước 1 sẽ được trả về khiid = 1L
. Kể từ đây,CustomerReader
class sẽ không hoàn toàn biết được rằngEntityManager
là fake. Nó chỉ việc gọifindFullName
(bước 4) và nhận về sample customer mà không quan tâm Mockito ở bên dưới tất cả.
Test case này thỏa mãn tất cả các yêu cầu của một unit test, nó không phụ thuộc bên ngoài, chỉ cần Java code, nhanh và hoàn toàn mang tính quyết định, hoàn toàn không cần database.
Thần chú When-Then trong Mockito
Sử dụng stubbing directive đơn giản
1 2 | <span class="token function">when</span><span class="token punctuation">(</span>something<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">thenReturn</span><span class="token punctuation">(</span>somethingElse<span class="token punctuation">)</span> |
sẽ giúp ích rất nhiều cho các unit test case của bạn. Tùy vào ứng dụng, đây có thể là tính năng (thần chú) Mockito duy nhất mà bạn cần.
Khi bạn có nhiều test methods, sẽ hợp lý khi di chuyển việc mock đến một mơi duy nhất và chỉ phân biệt hành vi của nó cho từ test riêng lẻ.
Ở ví dụ trước, có thể bạn đã nhận ra class CustomerReader
không chính xác, vì nó chưa xử lý trường hợp null
, ví dụ trường database ID không tồn tại trong database. Mặc dù chúng ta có thể copy-paste các unit test, tuy nhiên sẽ tốt hơn nếu chúng ta sắp xếp code với một common test method.
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 | <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CustomerReaderTest</span> <span class="token punctuation">{</span> <span class="token comment">//Class to be tested</span> <span class="token keyword">private</span> CustomerReader customerReader<span class="token punctuation">;</span> <span class="token comment">//Dependencies</span> <span class="token keyword">private</span> EntityManager entityManager<span class="token punctuation">;</span> <span class="token annotation punctuation">@Before</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setup</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span> customerReader <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">CustomerReader</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> entityManager <span class="token operator">=</span> <span class="token function">mock</span><span class="token punctuation">(</span>EntityManager<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span> customerReader<span class="token punctuation">.</span><span class="token function">setEntityManager</span><span class="token punctuation">(</span>entityManager<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Test</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">customerInDb</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span> Customer sampleCustomer <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Customer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> sampleCustomer<span class="token punctuation">.</span><span class="token function">setFirstName</span><span class="token punctuation">(</span><span class="token string">"Susan"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> sampleCustomer<span class="token punctuation">.</span><span class="token function">setLastName</span><span class="token punctuation">(</span><span class="token string">"Ivanova"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">when</span><span class="token punctuation">(</span>entityManager<span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span>Customer<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">,</span> <span class="token number">1</span>L<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">thenReturn</span><span class="token punctuation">(</span>sampleCustomer<span class="token punctuation">)</span><span class="token punctuation">;</span> String fullName <span class="token operator">=</span> customerReader<span class="token punctuation">.</span><span class="token function">findFullName</span><span class="token punctuation">(</span><span class="token number">1</span>L<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">assertEquals</span><span class="token punctuation">(</span><span class="token string">"Susan Ivanova"</span><span class="token punctuation">,</span>fullName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Test</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">customerNotPresentInDb</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token function">when</span><span class="token punctuation">(</span>entityManager<span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span>Customer<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">,</span> <span class="token number">1</span>L<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">thenReturn</span><span class="token punctuation">(</span>null<span class="token punctuation">)</span><span class="token punctuation">;</span> String fullName <span class="token operator">=</span> customerReader<span class="token punctuation">.</span><span class="token function">findFullName</span><span class="token punctuation">(</span><span class="token number">1</span>L<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">assertEquals</span><span class="token punctuation">(</span><span class="token string">""</span><span class="token punctuation">,</span> fullName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Ở ví dụ này, chúng ta di chuyển mock và common code vào setup
method, toàn bộ code trong method này sẽ chạy trước khi chạy mỗi test method. Sự khác nhau duy nhất giữa các test method là when
directive.
Basic Mocking với Mockito
Cùng xem trường hợp mock là cần thiết để thay thế một stub.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">LateInvoiceNotifier</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">final</span> EmailSender emailSender<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">final</span> InvoiceStorage invoiceStorage<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">LateInvoiceNotifier</span><span class="token punctuation">(</span><span class="token keyword">final</span> EmailSender emailSender<span class="token punctuation">,</span> <span class="token keyword">final</span> InvoiceStorage invoiceStorage<span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>emailSender <span class="token operator">=</span> emailSender<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>invoiceStorage <span class="token operator">=</span> invoiceStorage<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">notifyIfLate</span><span class="token punctuation">(</span>Customer customer<span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token keyword">if</span><span class="token punctuation">(</span>invoiceStorage<span class="token punctuation">.</span><span class="token function">hasOutstandingInvoice</span><span class="token punctuation">(</span>customer<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">{</span> emailSender<span class="token punctuation">.</span><span class="token function">sendEmail</span><span class="token punctuation">(</span>customer<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Class này có 2 external dependencies, và sử dụng constuctor injection.
Trong thực tế, InvoiceStorage
là web service kết nối với hệ thống CRM bên ngoài, hoạt động chậm. Như đã biết, một unit test không bao giờ sử dụng web service thực tế.
EmailSender
class cũng là một hệ thống ngoài từ một third-party cung cấp chức năng emai. Vậy nên, chúng ta phải mock nó.
Tuy nhiên, có một vấn đề là khi bạn cố gắng viết test case cho các lớp này, chả có gì để asserted. Method chúng ta muốn test notifyIfLate
là một phương thức void không trả về bất cứ gì. Vậy test nó như thế nào?
Trong trường hợp này chúng ta sẽ focus vào side effect của code. Ở đây, send một email là side effect. Email chỉ được gởi khi outstanding invoice customer
xuất hiện. Mockito cung cấp verify
directive cho việc testing side effect.
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 | <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">LateInvoiceNotifierTest</span> <span class="token punctuation">{</span> <span class="token comment">//Class to be tested</span> <span class="token keyword">private</span> LateInvoiceNotifier lateInvoiceNotifier<span class="token punctuation">;</span> <span class="token comment">//Dependencies (will be mocked)</span> <span class="token keyword">private</span> EmailSender emailSender<span class="token punctuation">;</span> <span class="token keyword">private</span> InvoiceStorage invoiceStorage<span class="token punctuation">;</span> <span class="token comment">//Test data</span> <span class="token keyword">private</span> Customer sampleCustomer<span class="token punctuation">;</span> <span class="token annotation punctuation">@Before</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setup</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span> invoiceStorage <span class="token operator">=</span> <span class="token function">mock</span><span class="token punctuation">(</span>InvoiceStorage<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span> emailSender <span class="token operator">=</span> <span class="token function">mock</span><span class="token punctuation">(</span>EmailSender<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span> lateInvoiceNotifier <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">LateInvoiceNotifier</span><span class="token punctuation">(</span>emailSender<span class="token punctuation">,</span>invoiceStorage<span class="token punctuation">)</span><span class="token punctuation">;</span> sampleCustomer <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Customer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> sampleCustomer<span class="token punctuation">.</span><span class="token function">setFirstName</span><span class="token punctuation">(</span><span class="token string">"Susan"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> sampleCustomer<span class="token punctuation">.</span><span class="token function">setLastName</span><span class="token punctuation">(</span><span class="token string">"Ivanova"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Test</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">lateInvoice</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token function">when</span><span class="token punctuation">(</span>invoiceStorage<span class="token punctuation">.</span><span class="token function">hasOutstandingInvoice</span><span class="token punctuation">(</span>sampleCustomer<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">thenReturn</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Stub</span> lateInvoiceNotifier<span class="token punctuation">.</span><span class="token function">notifyIfLate</span><span class="token punctuation">(</span>sampleCustomer<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">verify</span><span class="token punctuation">(</span>emailSender<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">sendEmail</span><span class="token punctuation">(</span>sampleCustomer<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// specified argument is sampleCustomer.</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Test</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">noLateInvoicePresent</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token function">when</span><span class="token punctuation">(</span>invoiceStorage<span class="token punctuation">.</span><span class="token function">hasOutstandingInvoice</span><span class="token punctuation">(</span>sampleCustomer<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">thenReturn</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Stub</span> lateInvoiceNotifier<span class="token punctuation">.</span><span class="token function">notifyIfLate</span><span class="token punctuation">(</span>sampleCustomer<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">verify</span><span class="token punctuation">(</span>emailSender<span class="token punctuation">,</span> <span class="token function">times</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">sendEmail</span><span class="token punctuation">(</span>sampleCustomer<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Như ở trên, chúng ta Stub InvoiceStorage
class bằng when-then
syntax. Cả 2 test method đều không sử dụng JUnit assert statement. Thay vào đó, chúng ta sử dụng verify
directive để kiểm tra mock sau mỗi lần chạy, và pass test case nếu một method được gọi với đúng các argument chỉ định.
Trong test method 2, chúng ta test trường hợp sendEmail
method không được gọi. Vì thế, times(0)
với 0
là số lần mà method sendEmail đã gọi. Default times = 1 sẽ có thể được bỏ qua (trong test method 1).
Chú ý rằng Mock vẫn có thể được Stub khi cần. Bạn có thể nghĩ rằng mock là superset (tập cha) của stub. Vì thế nên Mockito gọi cả 2 là Mock.
Verify arguments với Argument Captor
Trong ví dụ trước đó, chúng ta chỉ verify
đơn giản một method được gọi hay không. Đôi lúc chúng ta cần chi tiết hơn, ngoài việc method được gọi hay không, chúng ta verify argument của method được test.
Mockito cung cấp cho chúng ta ArgumentCaptor
để hỗ trợ việc kiểm tra method argument:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <span class="token keyword">class</span> <span class="token class-name">MathUtils</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">add</span><span class="token punctuation">(</span><span class="token keyword">int</span> x<span class="token punctuation">,</span> <span class="token keyword">int</span> y<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> x <span class="token operator">+</span> y<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Test</span> <span class="token keyword">void</span> <span class="token function">test</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> MathUtils mockMathUtils <span class="token operator">=</span> <span class="token function">mock</span><span class="token punctuation">(</span>MathUtils<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">when</span><span class="token punctuation">(</span>mockMathUtils<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">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">thenReturn</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span> ArgumentCaptor myCaptor <span class="token operator">=</span> ArgumentCaptor<span class="token punctuation">.</span><span class="token function">forClass</span><span class="token punctuation">(</span>Integer<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">assertEquals</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> mockMathUtils<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">1</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>mockMathUtils<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>myCaptor<span class="token punctuation">.</span><span class="token function">capture</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> myCaptor<span class="token punctuation">.</span><span class="token function">capture</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> List allValues <span class="token operator">=</span> myCaptor<span class="token punctuation">.</span><span class="token function">getAllValues</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">assertEquals</span><span class="token punctuation">(</span>List<span class="token punctuation">.</span><span class="token function">of</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">,</span> allValues<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> |
Trong ví dụ đơn giản ở trên, chúng ta sử dụng một ArgumentCaptor
và định nghĩa nó là holder của một Integer
class. Sau đó, trong verify
directive, chúng ta sử dụng captor
bằng cách gọi capture()
method.
Tại điểm này, khi unit test hoàn thành, captor sẽ chứa chính xác argument gởi đến mock MathUtils
khi method add()
được gọi. Chúng ta có thể extract instance của argument bằng cách gọi getValue()
hoặc toàn bộ argument bằng getAllValues()
.
Chú ý rằng argument có thể là bất kì một đối tượng Java phức tạp nào (vd: nested class, data structure /list…). Mockito có thể capture mà không gặp 1 vấn đề nào, và bạn có thể verify chúng theo cách bạn muốn.
Forming Dynamic Responses for Mocks
Sức mạnh tiếp theo của Mockito cho phép bạn có thể custom reponse từ mock mà phụ thuộc vào argument của lời gọi hàm. Đây là một kĩ thuật tiên tiến và chỉ cần thiết cho 1 vài trường hợp rất cụ thể trong unit test. Nếu được lựa chọn, tốt nhất là bạn nên trả về các kết quả được xác định trước thông qua mock/stub (như các ví dụ trước đó) để test của bạn trở để dễ đọc. Chỉ nên sử dụng Dynamic response
như là kế sách cuối cùng.
Dynamic Manipulation of Arguments
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CustomerDao</span> <span class="token punctuation">{</span> <span class="token annotation punctuation">@PersistenceContext</span> <span class="token keyword">private</span> EntityManager entityManager<span class="token punctuation">;</span> <span class="token keyword">private</span> Logger logger<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">saveCustomer</span><span class="token punctuation">(</span>String firstName<span class="token punctuation">,</span> String lastName<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>firstName <span class="token operator">==</span> null <span class="token operator">||</span> lastName<span class="token operator">==</span>null<span class="token punctuation">)</span> <span class="token punctuation">{</span> logger<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">"Missing customer information"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalArgumentException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> Customer customer <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Customer</span><span class="token punctuation">(</span>firstName<span class="token punctuation">,</span> lastName<span class="token punctuation">)</span><span class="token punctuation">;</span> entityManager<span class="token punctuation">.</span><span class="token function">persist</span><span class="token punctuation">(</span>customer<span class="token punctuation">)</span><span class="token punctuation">;</span> entityManager<span class="token punctuation">.</span><span class="token function">flush</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> logger<span class="token punctuation">.</span><span class="token function">info</span><span class="token punctuation">(</span><span class="token string">"Saved customer with id {}"</span><span class="token punctuation">,</span> customer<span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Bạn có thể thấy ngay việc unit test cho class này có một chút khó khăn. Mặc dù logic của DAO là rất cơ bản, nhưng vấn đề là khi một customer được lưu bằng persist()
method, database ID của nó được gởi đến logger
. Đối với ví dụ giả định này, code sẽ hoạt động tốt trong hệ thống thực tế, vì database sẽ gán ID cho đối tượng ngay khi customer được lưu. Tuy nhiên, làm thế nào chúng ta có thể sao chếp quá trình xử lý này trong unit test? Vì method persist không trả về argument nên chúng ta không thể mock nó với when-then
directive. Tuy nhiên, Mockito vẫn có giải pháp:
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 | <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CustomerDaoTest</span> <span class="token punctuation">{</span> <span class="token comment">// Class to be tested</span> <span class="token keyword">private</span> CustomerDao customerDao<span class="token punctuation">;</span> <span class="token comment">// Dependencies (will be mocked)</span> <span class="token keyword">private</span> EntityManager entityManager<span class="token punctuation">;</span> <span class="token keyword">private</span> Logger logger<span class="token punctuation">;</span> <span class="token annotation punctuation">@Before</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setup</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> customerDao <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">CustomerDao</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> entityManager <span class="token operator">=</span> <span class="token function">mock</span><span class="token punctuation">(</span>EntityManager<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span> customerDao<span class="token punctuation">.</span><span class="token function">setEntityManager</span><span class="token punctuation">(</span>entityManager<span class="token punctuation">)</span><span class="token punctuation">;</span> logger <span class="token operator">=</span> <span class="token function">mock</span><span class="token punctuation">(</span>Logger<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span> customerDao<span class="token punctuation">.</span><span class="token function">setLogger</span><span class="token punctuation">(</span>logger<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Test</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">happyPath</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">doAnswer</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Answer</span><span class="token generics function"><span class="token punctuation"><</span>Void<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> Void <span class="token function">answer</span><span class="token punctuation">(</span>InvocationOnMock invocation<span class="token punctuation">)</span> <span class="token punctuation">{</span> Customer customer <span class="token operator">=</span> invocation<span class="token punctuation">.</span><span class="token function">getArgument</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span> customer<span class="token punctuation">.</span><span class="token function">setId</span><span class="token punctuation">(</span><span class="token number">123</span>L<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> null<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">when</span><span class="token punctuation">(</span>entityManager<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">persist</span><span class="token punctuation">(</span><span class="token function">any</span><span class="token punctuation">(</span>Customer<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> customerDao<span class="token punctuation">.</span><span class="token function">saveCustomer</span><span class="token punctuation">(</span><span class="token string">"Suzan"</span><span class="token punctuation">,</span> <span class="token string">"Ivanova"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">verify</span><span class="token punctuation">(</span>logger<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">info</span><span class="token punctuation">(</span><span class="token string">"Saved customer with id {}"</span><span class="token punctuation">,</span> <span class="token number">123</span>L<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Test</span><span class="token punctuation">(</span>expected <span class="token operator">=</span> IllegalArgumentException<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">missingInformation</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> customerDao<span class="token punctuation">.</span><span class="token function">saveCustomer</span><span class="token punctuation">(</span><span class="token string">"Suzan"</span><span class="token punctuation">,</span> null<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Unit test trên dựa trên doAnswer-when
directive. Mockito cho phép chúng ta override để answer bất cứ method nào bằng cách implement Answer interface
. Interface này có duy nhất 1 method cho phép chúng ta truy cập argument được truyền từ unit test.
Trong trường hợp cụ thể trong ví dụ trên, chúng ta biết rõ argument là một customer. Chúng ta fetch customer và set database ID thành 123L, hoặc bất cứ giá trị nào bạn muốn. Chúng ta cũng hướng cho Mockito bind kết quả answer vào bất cứ any
argument kiểu Customer
. Ở đây, argument của persist method không được tạo bởi chúng ta, nhưng được tạo bở class được test, vì thế chúng ta không thể tạo một đối tượng mà dữ liệu test khớp với nó. Tuy nhiên, với Mockito doAnswer
directive, chúng ta không cần biết trước bất kì điều gì, vì chúng ta thay đổi chúng trong runtime.
Dynamic Responses Based on Arguments
Một ví dụ thực tế hơn, trong đó answer của một mock phụ thuộc vào argument.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">MassUserRegistration</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">final</span> EventRecorder eventRecorder<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">final</span> UserRepository userRepository<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">MassUserRegistration</span><span class="token punctuation">(</span><span class="token keyword">final</span> EventRecorder eventRecorder<span class="token punctuation">,</span> <span class="token keyword">final</span> UserRepository userRepository<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>eventRecorder <span class="token operator">=</span> eventRecorder<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>userRepository <span class="token operator">=</span> userRepository<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">register</span><span class="token punctuation">(</span>String firstName<span class="token punctuation">,</span> String lastName<span class="token punctuation">)</span> <span class="token punctuation">{</span> Customer newCustomer <span class="token operator">=</span> userRepository<span class="token punctuation">.</span><span class="token function">saveCustomer</span><span class="token punctuation">(</span>firstName<span class="token punctuation">,</span> lastName<span class="token punctuation">)</span><span class="token punctuation">;</span> Event event <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Event</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> event<span class="token punctuation">.</span><span class="token function">setTimestamp</span><span class="token punctuation">(</span>newCustomer<span class="token punctuation">.</span><span class="token function">getSince</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> event<span class="token punctuation">.</span><span class="token function">setCustomerName</span><span class="token punctuation">(</span>newCustomer<span class="token punctuation">.</span><span class="token function">getFullName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> event<span class="token punctuation">.</span><span class="token function">setType</span><span class="token punctuation">(</span>Type<span class="token punctuation">.</span>REGISTRATION<span class="token punctuation">)</span><span class="token punctuation">;</span> eventRecorder<span class="token punctuation">.</span><span class="token function">recordEvent</span><span class="token punctuation">(</span>event<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">massRegister</span><span class="token punctuation">(</span>List<span class="token generics function"><span class="token punctuation"><</span>Customer<span class="token punctuation">></span></span> rawCustomerNames<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">for</span> <span class="token punctuation">(</span>Customer customer<span class="token operator">:</span>rawCustomerNames<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">register</span><span class="token punctuation">(</span>customer<span class="token punctuation">.</span><span class="token function">getFirstName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>customer<span class="token punctuation">.</span><span class="token function">getLastName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Ví dụ này, class truyền 1 list customer và save vào UserRepository
. Với mỗi customer, một event type REGISTRATION
được phát hành.
Chúng ta cần test massRegister
method vì register
là private
. Về lý thuyết, chúng ta có thể truyền 1 list chỉ có 1 customer trong unit test case. Nhưng trong thực tế, tốt nhất vẫn nên thử với một list chứa danh sách rất lớn customer. Code có thể đơn giản và hoàn toàn không có test trường hợp lỗi, nhưng trong hệ thống thực tế có thể có một số kiểm tra tính nhất quán trước khi customer được register. Một unit test thực tế (realistic unit test
) cần truyền một danh sách lớn các customer với các vấn đề khác nhau, để tất cả các kiểm tra có thể được đánh giá trong quá trình unit testing.
Giả sử chúng ta muốn test list gồm 20 customer. Lúc saveRepository
method trả về argument, về lý thuyết chúng ta có thể dùng when-then
directive 20 lần để hướng nó chính xác output cần được gởi.
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 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">MassUserRegistrationTest</span> <span class="token punctuation">{</span> <span class="token comment">//Class to be tested</span> <span class="token keyword">private</span> MassUserRegistration massUserRegistration<span class="token punctuation">;</span> <span class="token comment">//Dependencies (will be mocked)</span> <span class="token keyword">private</span> UserRepository userRepository<span class="token punctuation">;</span> <span class="token keyword">private</span> EventRecorder eventRecorder<span class="token punctuation">;</span> <span class="token comment">//Test data</span> <span class="token keyword">private</span> List<span class="token generics function"><span class="token punctuation"><</span>Customer<span class="token punctuation">></span></span> sampleCustomers<span class="token punctuation">;</span> <span class="token annotation punctuation">@Before</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setup</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span> sampleCustomers <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token operator"><</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> eventRecorder <span class="token operator">=</span> <span class="token function">mock</span><span class="token punctuation">(</span>EventRecorder<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span> userRepository <span class="token operator">=</span> <span class="token function">mock</span><span class="token punctuation">(</span>UserRepository<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">when</span><span class="token punctuation">(</span>userRepository<span class="token punctuation">.</span><span class="token function">saveCustomer</span><span class="token punctuation">(</span><span class="token function">anyString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">anyString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">thenAnswer</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Answer</span><span class="token generics function"><span class="token punctuation"><</span>Customer<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> Customer <span class="token function">answer</span><span class="token punctuation">(</span>InvocationOnMock invocation<span class="token punctuation">)</span> <span class="token keyword">throws</span> Throwable <span class="token punctuation">{</span> String firstName <span class="token operator">=</span> invocation<span class="token punctuation">.</span><span class="token function">getArgument</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span> String lastName <span class="token operator">=</span> invocation<span class="token punctuation">.</span><span class="token function">getArgument</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> Customer newCustomer <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Customer</span><span class="token punctuation">(</span>firstName<span class="token punctuation">,</span> lastName<span class="token punctuation">)</span><span class="token punctuation">;</span> newCustomer<span class="token punctuation">.</span><span class="token function">setFullName</span><span class="token punctuation">(</span>firstName<span class="token operator">+</span><span class="token string">" "</span><span class="token operator">+</span>lastName<span class="token punctuation">)</span><span class="token punctuation">;</span> newCustomer<span class="token punctuation">.</span><span class="token function">setSince</span><span class="token punctuation">(</span>LocalDate<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> newCustomer<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> massUserRegistration <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">MassUserRegistration</span><span class="token punctuation">(</span>eventRecorder<span class="token punctuation">,</span>userRepository<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Test</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">registerTwentyAccounts</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span> sampleCustomers<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Customer</span><span class="token punctuation">(</span><span class="token string">"Susan"</span><span class="token punctuation">,</span> <span class="token string">"Ivanova"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> sampleCustomers<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Customer</span><span class="token punctuation">(</span><span class="token string">"Lyta"</span><span class="token punctuation">,</span> <span class="token string">"Alexander"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> sampleCustomers<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Customer</span><span class="token punctuation">(</span><span class="token string">"Vir"</span><span class="token punctuation">,</span> <span class="token string">"Cotto"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> sampleCustomers<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Customer</span><span class="token punctuation">(</span><span class="token string">"Stephen"</span><span class="token punctuation">,</span> <span class="token string">"Frankling"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//[...20 customers redacted for brevity...]</span> massUserRegistration<span class="token punctuation">.</span><span class="token function">massRegister</span><span class="token punctuation">(</span>sampleCustomers<span class="token punctuation">)</span><span class="token punctuation">;</span> ArgumentCaptor<span class="token generics function"><span class="token punctuation"><</span>Event<span class="token punctuation">></span></span> myCaptor <span class="token operator">=</span> ArgumentCaptor<span class="token punctuation">.</span><span class="token function">forClass</span><span class="token punctuation">(</span>Event<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">verify</span><span class="token punctuation">(</span>eventRecorder<span class="token punctuation">,</span> <span class="token function">times</span><span class="token punctuation">(</span>sampleCustomers<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">recordEvent</span><span class="token punctuation">(</span>myCaptor<span class="token punctuation">.</span><span class="token function">capture</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> List<span class="token generics function"><span class="token punctuation"><</span>Event<span class="token punctuation">></span></span> eventsThatWereSent <span class="token operator">=</span> myCaptor<span class="token punctuation">.</span><span class="token function">getAllValues</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">assertEquals</span><span class="token punctuation">(</span>sampleCustomers<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>eventsThatWereSent<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">;</span>i<span class="token operator"><</span> eventsThatWereSent<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>i<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span> Event event<span class="token operator">=</span> eventsThatWereSent<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">assertNotNull</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span><span class="token function">getTimestamp</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">assertEquals</span><span class="token punctuation">(</span>Event<span class="token punctuation">.</span>Type<span class="token punctuation">.</span>REGISTRATION<span class="token punctuation">,</span> event<span class="token punctuation">.</span><span class="token function">getType</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">assertEquals</span><span class="token punctuation">(</span>sampleCustomers<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getFirstName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span><span class="token string">" "</span><span class="token operator">+</span>sampleCustomers<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getLastName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>event<span class="token punctuation">.</span><span class="token function">getCustomerName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Trong test class, 2 việc cần lưu ý:
- Trong setup, chúng ta override
saveCustomer
method bằngAnswer
interface. Fetch 2 argument bằnganyString() matcher
, tạo Customer instance và fill thông tin cần thiết. - Theo đó, với bất kì kích thước của dữ liệu test,
UserRepository
mock sẽ luôn trả về response chính xác cho class được kiểm thử.
Lưu ý rằng, unit test được viết sao cho kích thước dữ liệu đầu vào thực sự không liên quan. Chúng ta có thể mở rộng dữ liệu kiểm thử từ 20 lên 100 hoặc 1000 customer, riêng mocking và verification code sẽ không đổi. Điều này không xảy ra nếu chúng ta tự đặt một response cho từng customer cụ thể.
Reference:
- https://semaphoreci.com/community/tutorials/stubbing-and-mocking-with-mockito-2-and-junit
- http://xunitpatterns.com/Test Stub.html
- https://javadoc.io/doc/org.mockito/mockito-core/2.28.2/org/mockito/Mockito.html
HAPPY CODING