Mockito so khớp là các phương thức tĩnh và các cuộc gọi đến các phương thức đó, các phương thức này thay thế cho các đối số trong khi gọi đến when
và verify
.
Trình so khớp Hamcrest (phiên bản lưu trữ) (hoặc Trình so khớp kiểu Hamcrest) là các trường hợp đối tượng không trạng thái, có mục đích chung, triển khai Matcher<T>
và hiển thị một phương thức matches(T)
trả về true nếu đối tượng phù hợp với tiêu chí của Matcher. Chúng nhằm mục đích không có tác dụng phụ và thường được sử dụng trong các khẳng định như dưới đây.
/* Mockito */ verify(foo).setPowerLevel(gt(9000));
/* Hamcrest */ assertThat(foo.getPowerLevel(), is(greaterThan(9000)));
Các trình so khớp Mockito tồn tại, tách biệt với các trình so khớp kiểu Hamcrest, để các mô tả của các biểu thức so khớp phù hợp trực tiếp với các lệnh gọi phương thức : Mockito so khớp trả về T
nơi các phương thức Hamcrest matcher trả về các đối tượng Matcher (thuộc loại Matcher<T>
).
Quẹt Mockito được gọi thông qua phương pháp tĩnh như eq
, any
, gt
, và startsWith
trên org.mockito.Matchers
và org.mockito.AdditionalMatchers
. Ngoài ra còn có các bộ điều hợp, đã thay đổi trên các phiên bản Mockito:
- Đối với Mockito 1.x,
Matchers
một số lệnh gọi (chẳng hạn như intThat
hoặc argThat
) là các trình so khớp Mockito trực tiếp chấp nhận các trình so khớp Hamcrest làm tham số. ArgumentMatcher<T>
mở rộng org.hamcrest.Matcher<T>
, được sử dụng trong biểu diễn Hamcrest nội bộ và là lớp cơ sở của Hamcrest matcher thay vì bất kỳ loại nào của Mockito matcher.
- Đối với Mockito 2.0+, Mockito không còn phụ thuộc trực tiếp vào Hamcrest.
Matchers
gọi phrased as intThat
hoặc argThat
wrap ArgumentMatcher<T>
đối tượng không còn triển khai org.hamcrest.Matcher<T>
nhưng được sử dụng theo những cách tương tự. Bộ điều hợp Hamcrest chẳng hạn như argThat
và intThat
vẫn có sẵn, nhưng MockitoHamcrest
thay vào đó đã chuyển sang .
Bất kể người ghép là Hamcrest hay đơn giản là kiểu Hamcrest, chúng có thể được điều chỉnh như vậy:
/* Mockito matcher intThat adapting Hamcrest-style matcher is(greaterThan(...)) */
verify(foo).setPowerLevel(intThat(is(greaterThan(9000))));
Trong câu lệnh trên: foo.setPowerLevel
là một phương thức chấp nhận một int
. is(greaterThan(9000))
trả về a Matcher<Integer>
, sẽ không hoạt động như một setPowerLevel
đối số. Mockito matcher intThat
kết thúc Matcher kiểu Hamcrest đó và trả về một int
để nó có thể xuất hiện như một đối số; Những người so khớp Mockito gt(9000)
muốn gói toàn bộ biểu thức đó thành một lệnh gọi, như trong dòng đầu tiên của mã ví dụ.
Những người đối sánh làm gì / quay lại
when(foo.quux(3, 5)).thenReturn(true);
Khi không sử dụng trình so khớp đối số, Mockito ghi lại các giá trị đối số của bạn và so sánh chúng với các equals
phương thức của chúng .
when(foo.quux(eq(3), eq(5))).thenReturn(true); // same as above
when(foo.quux(anyInt(), gt(5))).thenReturn(true); // this one's different
Khi bạn gọi một đối sánh như any
hoặc gt
(lớn hơn), Mockito lưu trữ một đối tượng đối sánh khiến Mockito bỏ qua kiểm tra bình đẳng đó và áp dụng đối sánh bạn chọn. Trong trường hợp của argumentCaptor.capture()
nó, nó lưu trữ một trình so khớp để lưu đối số của nó để kiểm tra sau này.
Trình so khớp trả về các giá trị giả chẳng hạn như số không, bộ sưu tập trống hoặc null
. Mockito cố gắng trả về một giá trị giả thích hợp, an toàn, như 0 cho anyInt()
hoặc any(Integer.class)
hoặc một giá trị trống List<String>
cho anyListOf(String.class)
. Tuy nhiên, vì tính năng xóa kiểu, Mockito thiếu thông tin kiểu để trả về bất kỳ giá trị nào nhưng null
vì any()
hoặc argThat(...)
, có thể gây ra NullPointerException nếu cố gắng "tự động mở hộp" một null
giá trị nguyên thủy.
Đối sánh thích eq
và gt
lấy các giá trị tham số; lý tưởng nhất, các giá trị này nên được tính toán trước khi bắt đầu khai báo / xác minh. Gọi một người giả đang chế giễu một cuộc gọi khác có thể ảnh hưởng đến việc khai gian.
Không thể sử dụng các phương thức đối sánh làm giá trị trả về; không có cách nào để diễn đạtthenReturn(anyInt())
hoặc thenReturn(any(Foo.class))
trong Mockito, chẳng hạn. Mockito cần biết chính xác thể hiện nào để trả về trong các cuộc gọi sơ khai và sẽ không chọn giá trị trả về tùy ý cho bạn.
Chi tiết triển khai
Các trình so khớp được lưu trữ (dưới dạng trình so khớp đối tượng kiểu Hamcrest) trong một ngăn xếp chứa trong một lớp được gọi là ArgumentMatcherStorage . MockitoCore và Matchers đều sở hữu một cá thể ThreadSafeMockingProgress , trong đó tĩnh chứa các cá thể MockingProgress đang giữ ThreadLocal. Đó là MockingProgressImpl này giữ một bê tông ArgumentMatcherStorageImpl . Do đó, trạng thái mô phỏng và đối sánh là tĩnh nhưng được phân luồng nhất quán giữa các lớp Mockito và Matchers.
Hầu hết các cuộc gọi khớp chỉ làm tăng thêm chồng này, với một ngoại lệ cho quẹt như and
, or
, vànot
. Điều này hoàn toàn tương ứng với (và dựa vào) thứ tự đánh giá của Java , thứ tự đánh giá các đối số từ trái sang phải trước khi gọi một phương thức:
when(foo.quux(anyInt(), and(gt(10), lt(20)))).thenReturn(true);
[6] [5] [1] [4] [2] [3]
Điều này sẽ:
- Thêm
anyInt()
vào ngăn xếp.
- Thêm
gt(10)
vào ngăn xếp.
- Thêm
lt(20)
vào ngăn xếp.
- Di chuyển
gt(10)
và lt(20)
và thêm and(gt(10), lt(20))
.
- Cuộc gọi
foo.quux(0, 0)
, mà (trừ khi được khai báo khác) trả về giá trị mặc định false
. Mockito nội bộ đánh dấu quux(int, int)
là cuộc gọi gần đây nhất.
- Lệnh gọi
when(false)
, loại bỏ đối số của nó và chuẩn bị khai báo phương thức quux(int, int)
được xác định trong 5. Hai trạng thái hợp lệ duy nhất là với độ dài ngăn xếp là 0 (bằng nhau) hoặc 2 (đối sánh) và có hai đối sánh trên ngăn xếp (bước 1 và 4), vì vậy Mockito khai báo phương thức với một trình any()
khớp nối cho đối số đầu tiên và and(gt(10), lt(20))
đối số thứ hai và xóa ngăn xếp.
Điều này thể hiện một số quy tắc:
Mockito không thể phân biệt được giữa quux(anyInt(), 0)
và quux(0, anyInt())
. Cả hai đều giống như một cuộc gọi đến quux(0, 0)
với một int matcher trên ngăn xếp. Do đó, nếu bạn sử dụng một trình so khớp, bạn phải khớp với tất cả các đối số.
Thứ tự cuộc gọi không chỉ quan trọng mà nó là thứ làm cho tất cả điều này hoạt động . Việc trích xuất các trình so khớp vào các biến thường không hoạt động, vì nó thường thay đổi thứ tự cuộc gọi. Tuy nhiên, việc trích xuất các đối sánh thành các phương thức hoạt động rất tốt.
int between10And20 = and(gt(10), lt(20));
/* BAD */ when(foo.quux(anyInt(), between10And20)).thenReturn(true);
// Mockito sees the stack as the opposite: and(gt(10), lt(20)), anyInt().
public static int anyIntBetween10And20() { return and(gt(10), lt(20)); }
/* OK */ when(foo.quux(anyInt(), anyIntBetween10And20())).thenReturn(true);
// The helper method calls the matcher methods in the right order.
Ngăn xếp thay đổi thường xuyên đến mức Mockito không thể cảnh sát nó cẩn thận. Nó chỉ có thể kiểm tra ngăn xếp khi bạn tương tác với Mockito hoặc một mô hình và phải chấp nhận các đối tác mà không cần biết liệu chúng được sử dụng ngay lập tức hay bị bỏ rơi vô tình. Về lý thuyết, ngăn xếp phải luôn trống bên ngoài lệnh gọi đến when
hoặc verify
, nhưng Mockito không thể tự động kiểm tra điều đó. Bạn có thể kiểm tra thủ công với Mockito.validateMockitoUsage()
.
Trong một lệnh gọi tới when
, Mockito thực sự gọi phương thức được đề cập, phương thức này sẽ ném ra một ngoại lệ nếu bạn đã khai báo phương thức để ném một ngoại lệ (hoặc yêu cầu các giá trị khác 0 hoặc không rỗng).
doReturn
và doAnswer
(v.v.) làm không gọi phương thức thực tế và thường là một phương pháp thay thế hữu ích.
Nếu bạn đã gọi một phương pháp giả ở giữa stubbing (ví dụ như để tính toán một câu trả lời cho một eq
khớp), Mockito sẽ kiểm tra độ dài đống chống lại rằng cuộc gọi thay vào đó, và có khả năng thất bại.
Nếu bạn cố gắng làm điều gì đó xấu, chẳng hạn như khai báo / xác minh một phương thức cuối cùng , Mockito sẽ gọi phương thức thực và cũng để lại các đối sánh bổ sung trên ngăn xếp . Lệnh final
gọi phương thức có thể không đưa ra một ngoại lệ, nhưng bạn có thể nhận được một lỗi không hợp lệ từ các đối tượng trùng khớp khi tiếp theo bạn tương tác với một mô hình.
Vấn đề chung
Không hợp lệUseOfMatchersException :
Kiểm tra để đảm bảo rằng mọi đối số đều có chính xác một lệnh gọi đối sánh, nếu bạn hoàn toàn sử dụng đối sánh và bạn chưa sử dụng một đối sánh bên ngoài lệnh gọi when
hoặc verify
. Các đối sánh không bao giờ được sử dụng làm giá trị trả về gốc hoặc trường / biến.
Kiểm tra để đảm bảo rằng bạn không gọi một mô hình như một phần của việc cung cấp đối số so khớp.
Kiểm tra để đảm bảo rằng bạn không cố gắng khai báo / xác minh một phương pháp cuối cùng bằng trình so khớp. Đó là một cách tuyệt vời để để một trình so khớp trên ngăn xếp và trừ khi phương pháp cuối cùng của bạn đưa ra một ngoại lệ, đây có thể là lần duy nhất bạn nhận ra phương pháp bạn đang chế nhạo là cuối cùng.
NullPointerException với các đối số nguyên thủy: (Integer) any()
trả về null trong khi any(Integer.class)
trả về 0; điều này có thể gây ra NullPointerException
nếu bạn đang mong đợi một int
thay vì một Số nguyên. Trong mọi trường hợp, hãy ưu tiên anyInt()
, nó sẽ trả về 0 và cũng bỏ qua bước tự động đấm bốc.
NullPointerException hoặc các ngoại lệ khác: Các lệnh gọi đến when(foo.bar(any())).thenReturn(baz)
sẽ thực sự gọi foo.bar(null)
, mà bạn có thể đã cố gắng ném một ngoại lệ khi nhận đối số null. Chuyển để doReturn(baz).when(foo).bar(any())
bỏ qua hành vi bị khai thác .
Xử lý sự cố chung
Sử dụng MockitoJUnitRunner , hoặc gọi một cách rõ ràng validateMockitoUsage
trong phương thức tearDown
hoặc của bạn @After
(mà trình chạy sẽ tự động làm cho bạn). Điều này sẽ giúp xác định xem bạn có sử dụng sai các đối sánh hay không.
Đối với mục đích gỡ lỗi, hãy thêm lệnh gọi validateMockitoUsage
trực tiếp vào mã của bạn. Điều này sẽ ném ra nếu bạn có bất kỳ thứ gì trên ngăn xếp, đó là một cảnh báo tốt về một triệu chứng xấu.