Argument Matcherهای Mockito: any()، eq() و موارد دیگر

Argument Matcherهای Mockito به شما اجازه میدهند فراخوانیهای متد را Stub و Verify کنید—بدون Hard-code کردن هر مقدارِ آرگومانِ Mockito. وقتی Matcherها را با when() و verify() جفت میکنید، میتوانید قوانینی مانند «هر رشتهای» یا «این ID دقیق» را بیان کنید؛ در حالی که تستها خوانا میمانند. این راهنما، Matcherهای داخلی، قاعده سازگاری که از InvalidUseOfMatchersException جلوگیری میکند، ArgumentCaptor و پیادهسازیهای ArgumentMatcher سفارشی را—با استفاده از APIهای فعلی Mockito 5.x با JUnit 5—مرور میکند.
نکات کلیدی
- Argument Matcherهای Mockito در
org.mockito.ArgumentMatchersزندگی میکنند و فقط درون when()، verify() و helperهای Stubbing مرتبط مانند doNothing().when() کار میکنند. - اگر آرگومانی از Matcher استفاده کند، هر آرگومانی در آن فراخوانی باید از Matcher استفاده کند. Literalها را با eq() بپیچید—بهجای پاس دادن مقادیر خام.
- Matcherهای تایپشده (anyString()، anyInt()، anyList()) را به any() خام برای Primitiveها و Referenceها ترجیح دهید تا از غافلگیریهای null/Unboxing اجتناب کنید.
- از ArgumentCaptor وقتی استفاده کنید که لازم است بعد از فراخوانی ببینید چه چیزی پاس شده. از argThat() با ArgumentMatcher سفارشی وقتی استفاده کنید که منطق Matching قابل استفاده مجددِ Inline لازم دارید.
- Matcherها انتظارات را روی Stack داخلی ثبت و مقادیر ساختگی (Dummy) برمیگردانند؛ پس هرگز بیرون از عبارت Stubbing یا Verification صداشان نزنید.
پیشنیازها
- Java 11 یا جدیدتر. اگر Runtime محلی لازم دارید، راهنمای «نحوه نصب جاوا با Apt روی اوبونتو» در پارمین کلود را دنبال کنید.
- پروژه Maven یا Gradle با JUnit 5 و Mockito 5.x. مثالهای زیر از Mockito 5.14.2 و JUnit Jupiter 5.10.2 استفاده میکنند.
- آشنایی با Mockهای پایه از راهنمای «مثالهای Mock در Mockito» و Verification از راهنمای «Verify در Mockito» در پارمین کلود.
- پسزمینه اختیاری: راهنمای «آموزش JUnit 5» و راهنمای گستردهتر «آموزش Mockito» در پارمین کلود.
وابستگیهای تست Maven:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>5.14.2</version>
<scope>test</scope>
</dependency>
Import کردن Matcherها بهصورت Static در کلاسهای تست:
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
Argument Matcherهای Mockito چیستند؟
Argument Matcherهای Mockito، متدهای Helperای هستند که شرایط انعطافپذیری را برای پارامترهای متد حین Stubbing یا Verification توصیف میکنند. بهجای Matching با equals() روی مقادیر دقیق، میتوانید هر String، هر int مثبت یا قانون دامنهای سفارشی را بپذیرید.
Argument Matcherها چگونه در فریمورک Mockito جای میگیرند
Mockito، Matcherها را روی Stack داخلیای ثبت میکند وقتی Stub یا Verificationای میسازید. متدهایی مانند anyInt() مقادیر Dummy امنی برمیگردانند (مثلاً 0 برای anyInt()) تا کامپایلر جاوا عبارتِ درون when(mock.get(anyInt())) را بپذیرد. Matching واقعی وقتی رخ میدهد که Mockito Matcherهای ثبتشده را در برابر آرگومانهای فراخوانی واقعی بازپخش کند. مرجع رسمی: Javadoc مربوط به ArgumentMatchers.
چه زمانی از Matcher در مقابل مقادیر دقیق استفاده کنیم
از مقادیر دقیق (یا eq()) وقتی استفاده کنید که تست به یک ورودی خاص اهمیت میدهد. از Matcherها وقتی استفاده کنید که مقدار بیاهمیت است، بین موارد متغیر است یا از الگویی پیروی میکند (نوع، بازه، زیررشته). استفاده بیشازحد از any() میتواند باگها را پنهان کند؛ پس پیشفرض روی مقادیر دقیق باشید و وقتی Matcherها تکرار را کم میکنند سراغشان بروید.
کلاس نمونه استفادهشده در سراسر این آموزش:
public class Foo {
public boolean bool(String str, int i, Object obj) {
return false;
}
public int in(boolean b, java.util.List<String> strs) {
return 0;
}
public int bar(byte[] bytes, String[] s, int i) {
return 0;
}
public void log(String message) {
// no-op
}
}
قاعده سازگاری Argument Matcher
Mockito الزامی میکند که یا همه آرگومانها در Stubbing یا Verification از Matcher استفاده کنند، یا هیچکدام. ترکیب Matcher با Literal خام، InvalidUseOfMatchersException را Trigger میکند.
چرا نمیتوان Matcherها و مقادیر دقیق را ترکیب کرد
Matcherها و Literalها مسیرهای کدی متفاوتی درون Mockito دنبال میکنند. فراخوانیای مثل when(mock.bool(anyString(), 1, any())) نامعتبر است؛ چون 1 مقدار خام است در حالی که بقیه پارامترها از Matcher استفاده میکنند.
نادرست:
when(mockFoo.bool(anyString(), 1, any(Object.class))).thenReturn(true); // پرتاب میکند
درست:
when(mockFoo.bool(anyString(), eq(1), any(Object.class))).thenReturn(true);
همین قاعده روی verify() هم اعمال میشود:
verify(mockFoo).bool(eq("hello"), anyInt(), any(Object.class));
نحوه رفع InvalidUseOfMatchersException
- هر Literal را با eq() بپیچید:
eq(1)یاeq("hello"). - یا همه Matcherها را حذف و فقط مقادیر مشخص پاس بدهید.
- هرگز نتایج Matcher را در متغیرها ذخیره و بین Stubbingها استفاده مجدد نکنید.
Argument Matcherهای داخلی در Mockito
جدول زیر Matcherهای پرکاربرد در Mockito 5.x را خلاصه میکند. برای Referenceهای null، Matcherهای تایپشدهای مانند anyString() با null تطبیق نمییابند؛ بهجایش از isNull() یا isNotNull() استفاده کنید:
| Matcher | انواع پذیرفتهشده | رفتار null | مورد استفاده معمول |
|---|---|---|---|
| any() | هر Reference، Varargs | null را مجاز میداند | Matching loosen Reference |
| any(Class) | نوع T | null را مستثنی میکند | Matching آبجکتِ Type-Safe |
| eq(value) | همان نوعِ value | از equals() پیروی میکند | مقدار دقیق درون زنجیرههای Matcher |
| anyString() | String | null را مستثنی میکند | هر رشته غیر-null |
| anyInt() | int یا Integer | Wrapperِ null را مستثنی میکند | int Primitive یا Boxed |
| anyList() | List | null را مستثنی میکند | هر List غیر-null |
| isNull() / isNotNull() | انواع Reference | چکهای null صریح | پارامترهای Nullable |
| contains()، startsWith()، endsWith() | String | null را مستثنی میکند | Matching جزئی رشته |
استفاده از any() و any(Class) برای Matching مبتنی بر تایپ
any() با هر Referenceای—including null—مطابقت مییابد (و Varargs را پشتیبانی میکند). any(Foo.class) بررسی نوعی انجام و null را رد میکند. وقتی شفافیت مهم است any(Class) را ترجیح دهید:
Foo mockFoo = mock(Foo.class);
when(mockFoo.bool(anyString(), anyInt(), any(Object.class))).thenReturn(true);
assertTrue(mockFoo.bool("A", 1, "A"));
assertTrue(mockFoo.bool("B", 10, new Object()));
برای آرایهها، کلاسِ آرایه را پاس بدهید:
when(mockFoo.bar(any(byte[].class), any(String[].class), anyInt())).thenReturn(1);
استفاده از eq() برای تطبیق مقادیر دقیق درون زنجیرههای Matcher
وقتی هر پارامتری از Matcher استفاده میکند، مقادیر خاص را با eq() بپیچید:
when(mockFoo.bool(eq("false"), anyInt(), any(Object.class))).thenReturn(false);
assertFalse(mockFoo.bool("false", 10, new Object()));
بدون Matcherهای دیگر، میتوانید Literalها را مستقیم پاس بدهید: when(mockFoo.bool("false", 10, obj)).
استفاده از anyString()، anyInt()، anyList() و سایر Matcherهای تایپشده
Matcherهای تایپشده خوانایی را بهبود و از دامهای Autoboxing روی Primitiveها اجتناب میکنند:
when(mockFoo.in(anyBoolean(), anyList())).thenReturn(10);
برای Collectionها و Mapها هم anySet()، anyMap() و anyCollection() وجود دارند.
استفاده از isNull() و isNotNull()
when(mockFoo.bool(isNull(), anyInt(), isNotNull())).thenReturn(true);
assertTrue(mockFoo.bool(null, 1, "payload"));
استفاده از contains()، startsWith() و endsWith() برای Matching رشته
when(mockFoo.bool(startsWith("ERR"), anyInt(), any())).thenReturn(false);
mockFoo.bool("ERR-404", 0, null);
verify(mockFoo).bool(contains("ERR"), anyInt(), any());
استفاده از Argument Matcherها با when() برای Stubbing
Stubbing با any() و eq() با هم
Foo mockFoo = mock(Foo.class);
when(mockFoo.bool(anyString(), anyInt(), any(Object.class))).thenReturn(true);
when(mockFoo.bool(eq("false"), anyInt(), any(Object.class))).thenReturn(false);
Stubbing به ترتیب اعلان ارزیابی میشود؛ اگر الگوها همپوشانی دارند، خاصترین Stub باید آخر ظاهر شود.
Stubbing متدهای چندپارامتری (متدهای void)
برای متدهای void، از doNothing() استفاده کنید تا Matcherها به همان شکل کار کنند:
doNothing().when(mockFoo).log(anyString());
mockFoo.log("ready");
استفاده از Argument Matcherها با verify() برای Verification رفتار
Argument Matcherها فقط با verify() (و APIهای Stubbing) کار میکنند—نه بهعنوان چکهای بولی عمومی.
Verify کردن فراخوانی متدی با انواع آرگومان خاص (انواع پارامتر)
verify(mockFoo, atLeastOnce()).bool(anyString(), anyInt(), any(Object.class));
verify(mockFoo).bool(eq("false"), anyInt(), any(Object.class));
ترکیب verify() با times()، never() و atLeast()
verify(mockFoo, times(1)).log(anyString());
verify(mockFoo, never()).bool(eq("skip"), anyInt(), any());
verify(mockFoo, atLeast(0)).in(anyBoolean(), anyList());
برای Assert کردن ترتیب فراخوانی، از InOrder استفاده کنید:
InOrder inOrder = inOrder(mockFoo);
inOrder.verify(mockFoo).log(anyString());
inOrder.verify(mockFoo).bool(anyString(), anyInt(), any());
ثبت آرگومانها با ArgumentCaptor
ArgumentCaptor آرگومانهای پاسشده به Mock را ثبت میکند تا بعد از واقع شدن روی آنها Assert بگیرید. مکمل Matcherهاست: Matcherها قوانین پذیرش را بیان میکنند؛ Captorها مقادیر واقعی را بازرسی میکنند.
چه زمانی از ArgumentCaptor بهجای Matcher استفاده کنیم
از Captor وقتی استفاده کنید که به چند Assertion روی همان آبجکت بعد از فراخوانی نیاز دارید (فیلدها، حجم، مقادیر مشتقشده). از Matcherها وقتی استفاده کنید که Predicateای حین Stubbing یا Verification کافی است.
ArgumentCaptor<String> messageCaptor = ArgumentCaptor.forClass(String.class);
mockFoo.log("shipped");
verify(mockFoo).log(messageCaptor.capture());
assertEquals("shipped", messageCaptor.getValue());
ArgumentCaptor با فراخوانیهای منفرد و متعدد
mockFoo.log("first");
mockFoo.log("second");
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(mockFoo, times(2)).log(captor.capture());
assertEquals(List.of("first", "second"), captor.getAllValues());
ArgumentCaptor در مقابل ArgumentMatcher سفارشی
| رویکرد | مناسب برای | بدهبستان |
|---|---|---|
| ArgumentCaptor | بازرسی Instanceهای واقعی آرگومان بعد از فراخوانی | قدم verify اضافی، برای Stubbing مقادیر بازگشتی نیست |
| argThat(ArgumentMatcher) | قوانین Inline قابل استفاده مجدد حین Stub/Verify | دید کمتر به مقادیر واقعی مگر Verification شکست بخورد |
| eq() / any*() تایپشده | تستهای ساده و سریع | بیان کمتر برای قوانین دامنهای پیچیده |
نوشتن ArgumentMatcher سفارشی
اینترفیس org.mockito.ArgumentMatcher<T> را وقتی پیاده کنید که Matcherهای داخلی کافی نباشند. منطق را با argThat() ثبت کنید.
پیادهسازی اینترفیس ArgumentMatcher
import org.mockito.ArgumentMatcher;
public class PremiumOrderMatcher implements ArgumentMatcher<Order> {
@Override
public boolean matches(Order order) {
return order != null && order.getTotalCents() >= 10_000;
}
}
مثال Matcher سفارشی با آبجکت دامنهای
public class Order {
private final int totalCents;
public Order(int totalCents) { this.totalCents = totalCents; }
public int getTotalCents() { return totalCents; }
}
public class OrderService {
public boolean isPremium(Order order) {
return order != null && order.getTotalCents() >= 10_000;
}
}
ثبت و استفاده از Matcher سفارشی در تستها
OrderService service = mock(OrderService.class);
when(service.isPremium(argThat(new PremiumOrderMatcher()))).thenReturn(true);
assertTrue(service.isPremium(new Order(15_000)));
فرم Lambda (Java 8+):
when(service.isPremium(argThat(o -> o.getTotalCents() > 5_000))).thenReturn(true);
برای نکات طراحی از تیم Mockito، Javadoc مربوط به ArgumentMatcher را ببینید.
AdditionalMatchers مربوط به Mockito
org.mockito.AdditionalMatchers مقایسههای عددی و Helperهای برابری آرایه فراهم میکند:
import static org.mockito.AdditionalMatchers.*;
when(mockFoo.bar(any(byte[].class), aryEq(new String[] { "A", "B" }), gt(10))).thenReturn(11);
assertEquals(11, mockFoo.bar("abc".getBytes(), new String[] { "A", "B" }, 20));
سایر Helperها شامل lt()، or()، and() و not() برای شرایط ترکیبیاند.
اشتباهات رایج و نحوه اجتناب از آنها
ترکیب Matcherها و مقادیر خام
وقتی هر پارامتر خواهری از Matcher استفاده میکند، همیشه Literalها را با eq() تبدیل کنید.
استفاده از Argument Matcherها بیرون از when() یا verify()
String value = anyString(); // نادرست: matcher بیرون از stub/verify استفاده شده
Matcherها باید مستقیماً درون فراخوانی متدِ Mock-شدهای که به when() یا verify() پاس میشود ظاهر شوند.
استفاده بیشازحد از any() و پنهان کردن باگهای واقعی
برای پارامترهای حیاتی-برای-کسبوکار (IDها، Currencyها) مقادیر دقیق را ترجیح دهید. از Matcherها برای حذف نویز استفاده کنید—نه برای رد کردن Assertionهای مهم.
Argument Matcherهای Mockito در مقابل Matcherهای Hamcrest
Mockito از Hamcrest در نسخه 2.1 جدا شد. برای استفاده از Hamcrest، artifact مربوط به mockito-hamcrest را اضافه و بهجای APIهای منسوخِ org.mockito.Matchers از MockitoHamcrest.argThat(org.hamcrest.Matcher) صدا بزنید. برای بیشتر پروژهها، داخلیهای Mockito بههمراه argThat(ArgumentMatcher) سادهتر میمانند و از وابستگی اضافی اجتناب میکنند.
| ابزار | نقطه ورود | چه زمانی انتخاب کنیم |
|---|---|---|
| ArgumentMatchers مربوط به Mockito | any()، eq()، contains() | انتخاب پیشفرض برای تستهای Mockito |
| ArgumentMatcher سفارشی + argThat() | argThat(predicate) | قوانین خاص-دامنهای مشترک بین تستها |
| Hamcrest از طریق MockitoHamcrest | MockitoHamcrest.argThat(hasItem(…)) | Assertionهای Hamcrest موجودی که از قبل نگه میدارید |
انتخاب ابزار درست برای کار
| نیاز | استفاده |
|---|---|
| نادیده گرفتن مقدار پارامتر | anyString()، anyInt()، any(MyDto.class) |
| یک مقدار دقیق بین Matcherها | eq(“literal”) |
| بازرسیِ آنچه پاس شده | ArgumentCaptor |
| قانون پیچیده قابل استفاده مجدد | ArgumentMatcher سفارشی از طریق argThat() |
| Stub متد void | doNothing().when(mock).method(any()) |
سوالات متداول
۱. Argument Matcherها در Mockito چیستند؟
Argument Matcherها متدهای Staticای مانند anyString()، eq() و argThat() هستند که به Mockito میگویند حین Stubbing یا Verification چگونه پارامترها را مقایسه کند. آنها Matching انعطافپذیری را وقتی مقادیر دقیق ناشناخته یا بیاهمیتاند ممکن میسازند. اگر برای پارامتری از Matcher استفاده کنید، هر پارامتری در آن فراخوانی باید از Matcher استفاده کند.
۲. تفاوت eq() و any() چیست؟
eq(value) آرگومانی را لازم دارد که طبق equals() (یا == برای Primitiveها) با value برابر باشد. any() و variantهای تایپشدهاش مانند anyString() طیف گستردهای از ورودیها را میپذیرند که قوانین نوع یا زیررشته را ارضا کنند. از eq() وقتی استفاده کنید که فقط یک ورودی خاص باید مطابقت یابد؛ از any*() وقتی که مقدار دقیق مهم نیست.
۳. هنگام استفاده از Matcherها، آیا همه آرگومانها باید از Matcher استفاده کنند؟
بله. Mockito سازگاری Matcher را روی هر Stubbing یا فراخوانی verify() الزامی میکند. ترکیب anyString() با Literal خام باعث InvalidUseOfMatchersException میشود. Literalها را با eq() بپیچید یا برای آن فراخوانی، Matcherها را کاملاً حذف کنید.
۴. چه زمانی باید از eq() در Mockito استفاده کنم؟
از eq() وقتی استفاده کنید که حداقل یک پارامتر دیگر در همان فراخوانی از قبل از Matcher استفاده میکند و برای این پارامتر به مقدار دقیقی نیاز دارید. وقتی هر آرگومان Literal سادهای است و Matcherای وجود ندارد، میتوانید eq() را حذف کنید.
۵. any() در Mockito چه میکند؟
any() با هر Reference آبجکتی (شامل null) برای پارامترهای Reference مطابقت مییابد. جایگزینهای تایپشده مانند anyInt() یا any(Order.class) تطبیقها را به Primitiveها یا Instanceهای کلاسی محدود میکنند و—از Mockito 2.1.0—null را برای آن Matcherهای تایپشده مستثنی میکنند. برای شفافیت و تستهای امنتر، Matcherهای تایپشده را ترجیح دهید.
نتیجهگیری
Argument Matcherهای Mockito به شما کمک میکنند تستهای واحد متمرکزی بنویسید—با Stub و Verify کردن قوانین پارامتری انعطافپذیر با any()، eq()، Matcherهای تایپشده، ArgumentCaptor و پیادهسازیهای ArgumentMatcher سفارشی. قاعده همه-یا-هیچِ Matcher را به یاد داشته باشید؛ برای Primitiveها و Collectionها Matcherهای تایپشده را ترجیح بدهید؛ و وقتی لازم است وضعیت واقعی آرگومان را بعد از فراخوانی بازرسی کنید، سراغ Captorها بروید.




