0Pricing
Testing Mastery: JUnit, Mockito & Integration Tests · درس

مطابقات المعاملات في Mockito

استخدم مطابقات المعاملات لجعل عمليات التحقق وتهيئة السلوك أكثر مرونة ومتانة.

مطابقات المعاملات في Mockito درس مجاني في Testing Mastery: JUnit, Mockito & Integration Tests على CoddyKit. هذا هو الدرس 2 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Testing Mastery: JUnit, Mockito & Integration Tests، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Testing Mastery: JUnit, Mockito & Integration Tests 4 دروس في المجموع.

بعض أجزاء هذا الدرس لم تُترجم بعد وتظهر باللغة الإنجليزية.

Flexible Matching with Mockito

When writing unit tests with Mockito, you often need to verify method calls or configure mock behavior based on the arguments passed to them. Sometimes, an exact match for every argument isn't flexible enough.

Argument matchers are special placeholders that allow you to specify criteria for arguments instead of their precise values, making your tests more robust and less brittle.

Why Exact Matches Fall Short

By default, Mockito uses the equals() method to compare arguments. This works perfectly for simple types like numbers or strings, and for objects that correctly override equals().

  • What if you don't care about the exact object, but only a specific property within it?
  • What if an argument is a new object each time, but its content is what matters?

In these cases, relying solely on exact matches can make your tests fragile.

The Wildcard Matcher: `any()`

The simplest and most commonly used argument matcher is any(). It tells Mockito to match any value of a specified type for that argument position.

Use any() when you're only concerned that a method was called with some value of the correct type, not its precise content.

import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.*;

interface MyService {
  void processData(String message, int count);
}

public class AnyMatcherDemo {
  @Test
  void testAnyArgument() {
    MyService mockService = mock(MyService.class);
    mockService.processData("Hello", 5);

    // Verify processData was called with ANY String and ANY int
    verify(mockService).processData(anyString(), anyInt());
    System.out.println("Verified with anyString() and anyInt()!");
  }

  public static void main(String[] args) {
    new AnyMatcherDemo().testAnyArgument();
  }
}

Type-Specific `any()` Matchers

Mockito provides specific any() matchers for common data types, which improves readability and type safety:

  • anyString(): Matches any String.
  • anyInt(): Matches any int.
  • anyBoolean(): Matches any boolean.
  • anyList(), anySet(), anyMap(): Matches any collection type.
  • any(MyClass.class): Matches any instance of MyClass.

It's good practice to use the most specific matcher available.

Mixing `eq()` and Matchers

You can combine argument matchers with exact value matching. However, there's a crucial rule: once you use an argument matcher for *any* argument in a method call, you must use matchers for *all* arguments in that call.

Use eq(value) when you need an exact match for a specific argument, but other arguments are using matchers.

import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.*;

interface MyProcessor {
  void handle(String type, int id);
}

public class EqAndAnyDemo {
  @Test
  void testSpecificAndAny() {
    MyProcessor mockProcessor = mock(MyProcessor.class);
    mockProcessor.handle("EMAIL", 101);
    mockProcessor.handle("SMS", 202);

    // Verify handle was called with "EMAIL" as type and ANY int as id
    verify(mockProcessor).handle(eq("EMAIL"), anyInt());
    System.out.println("Verified handle('EMAIL', anyInt())!");
  }

  public static void main(String[] args) {
    new EqAndAnyDemo().testSpecificAndAny();
  }
}

Matching by Type and Nullability

Beyond exact types or any value, Mockito offers matchers for more specific conditions:

  • isA(Class clazz): Matches any object that is an instance of the given class (or a subclass). Functionally similar to any(Class.class).
  • notNull(): Matches any argument that is not null.
  • isNull(): Matches any argument that *is* null.

These provide fine-grained control over argument expectations.

Advanced Conditions: `and()`, `or()`

For complex matching scenarios, you can combine multiple matchers using logical operators. These are available from org.mockito.AdditionalMatchers (or sometimes directly from Mockito in newer versions).

  • and(matcher1, matcher2): Matches if both matchers are true.
  • or(matcher1, matcher2): Matches if either matcher is true.
  • not(matcher): Inverts the result of a matcher.

This allows you to build very specific argument conditions.

import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.*;
import static org.mockito.AdditionalMatchers.*; // For and/or/gt/lt

interface OrderService {
  void placeOrder(int customerId, double amount);
}

public class CombinedMatchersDemo {
  @Test
  void testCombinedMatchers() {
    OrderService mockService = mock(OrderService.class);
    mockService.placeOrder(101, 150.0);
    mockService.placeOrder(105, 50.0);

    // Verify calls where customerId > 100 AND amount > 100.0
    verify(mockService).placeOrder(
        and(gt(100), lt(1000)), // customerId between 101 and 999
        gt(100.0)
    );
    System.out.println("Verified order with customerId > 100 AND amount > 100!");
  }

  public static void main(String[] args) {
    new CombinedMatchersDemo().testCombinedMatchers();
  }
}

Tailored Matching with `argThat()`

When built-in matchers don't cover your needs, argThat() comes to the rescue. It allows you to define custom matching logic using a Hamcrest matcher or a simple lambda expression.

This is extremely powerful for comparing complex objects based on multiple properties or internal states.

import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.*;

class Product {
  String name;
  double price;
  Product(String name, double price) { this.name = name; this.price = price; }
  public String getName() { return name; }
  public double getPrice() { return price; }
}

interface ProductRepository {
  void save(Product product);
}

public class ArgThatDemo {
  @Test
  void testArgThatMatcher() {
    ProductRepository mockRepo = mock(ProductRepository.class);
    mockRepo.save(new Product("Laptop", 1200.0));
    mockRepo.save(new Product("Mouse", 25.0));

    // Verify save was called with a product whose price is > 1000
    verify(mockRepo).save(argThat(p -> p.getPrice() > 1000.0));
    System.out.println("Verified product with price > 1000 was saved!");
  }

  public static void main(String[] args) {
    new ArgThatDemo().testArgThatMatcher();
  }
}

Stubbing with Matchers

Argument matchers are not just for verification. You can also use them when stubbing mock behavior with when().thenReturn(). This allows your mock to return different values or throw exceptions based on the arguments it receives.

This is perfect for simulating various scenarios where a dependency behaves differently based on input.

import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.*;

interface DataService {
  String getData(String key);
}

public class StubbingWithMatchers {
  @Test
  void testStubbingWithAny() {
    DataService mockService = mock(DataService.class);

    // Stubbing: if getData is called with "config", return "Config Data"
    when(mockService.getData(eq("config"))).thenReturn("Config Data");
    // Stubbing: if getData is called with ANY other String, return "Default Data"
    when(mockService.getData(anyString())).thenReturn("Default Data");

    System.out.println("Result 1: " + mockService.getData("config"));
    System.out.println("Result 2: " + mockService.getData("user_profile"));
  }

  public static void main(String[] args) {
    new StubbingWithMatchers().testStubbingWithAny();
  }
}

Verification with Matchers

Argument matchers are fundamental for verifying that methods were called with arguments matching specific criteria. This ensures that your code interacts with its dependencies as expected, even if the exact argument values are dynamic.

You can combine matchers with verification modes like times(count) or atLeast(count) for more powerful assertions.

import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.*;

interface Logger {
  void log(String level, String message);
}

public class VerificationWithMatchers {
  @Test
  void testVerificationWithAny() {
    Logger mockLogger = mock(Logger.class);
    mockLogger.log("INFO", "User logged in.");
    mockLogger.log("ERROR", "Failed to save.");
    mockLogger.log("INFO", "Data processed.");

    // Verify 'log' was called twice with "INFO" and ANY String message
    verify(mockLogger, times(2)).log(eq("INFO"), anyString());
    System.out.println("Verified 2 INFO logs with any message!");
  }

  public static void main(String[] args) {
    new VerificationWithMatchers().testVerificationWithAny();
  }
}

Matcher Challenge

Consider the following Mockito setup and method calls:

MyDatabase mockDb = mock(MyDatabase.class);
mockDb.saveRecord("user_1", "John Doe");
mockDb.saveRecord("user_2", "Jane Smith");

Which verify() call would correctly assert that saveRecord was called at least once with a key that starts with "user_" and *any* string for the value?

Recap: Argument Matchers

In this lesson, you mastered Mockito's argument matchers, a powerful feature for creating flexible and robust tests:

  • We explored any() and its type-specific variants like anyString() for matching any value.
  • You learned how to combine eq() with other matchers for specific values.
  • We covered advanced matchers like isA(), notNull(), and logical operators (and(), or()).
  • You saw how argThat() allows for completely custom matching logic.
  • Finally, we applied matchers to both stubbing (when()) and verification (verify()) scenarios.

By using argument matchers effectively, your tests become less dependent on exact, rigid values and more focused on the expected behavior, making them more resilient to changes.

الأسئلة الشائعة

هل درس «مطابقات المعاملات في Mockito» مجاني؟

نعم — نص درس «مطابقات المعاملات في Mockito» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Testing Mastery: JUnit, Mockito & Integration Tests، انتقل إلى CoddyKit PRO. تتضمن دورة Testing Mastery: JUnit, Mockito & Integration Tests 4 دروس في المجموع.

ماذا ستتعلم في «مطابقات المعاملات في Mockito»؟

استخدم مطابقات المعاملات لجعل عمليات التحقق وتهيئة السلوك أكثر مرونة ومتانة. تتمرن على Testing Mastery: JUnit, Mockito & Integration Tests مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ Testing Mastery: JUnit, Mockito & Integration Tests؟

لا تُشترط خبرة سابقة. Testing Mastery: JUnit, Mockito & Integration Tests على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 4.

كم من الوقت يستغرق درس «مطابقات المعاملات في Mockito»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس Testing Mastery: JUnit, Mockito & Integration Tests هذا؟

نعم. كل درس في Testing Mastery: JUnit, Mockito & Integration Tests يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. تهيئة القيم المُعادة
  2. مطابقات المعاملات في Mockito
  3. مراقبة الكائنات الحقيقية
  4. إطلاق الاستثناءات والاستدعاءات المتتالية
← العودة إلى Testing Mastery: JUnit, Mockito & Integration Tests