0Pricing
Testing Mastery: JUnit, Mockito & Integration Tests · 课时

模拟静态方法与构造函数

探索使用 `mockito-inline` 等 Mockito 扩展模拟静态方法、最终类和构造函数的高级技术。

模拟静态方法与构造函数 是 CoddyKit 上的免费 Testing Mastery: JUnit, Mockito & Integration Tests 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Testing Mastery: JUnit, Mockito & Integration Tests 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Testing Mastery: JUnit, Mockito & Integration Tests 课程共包含 4 节课。

本课时的部分内容尚未翻译,以英文显示。

Beyond Regular Mocks

Welcome! So far, you've learned to mock interfaces and regular classes using Mockito. But what about those tricky bits of code that seem resistant to testing?

Sometimes, you encounter static methods, final classes, or direct constructor calls (new MyObject()) within the code you want to test. Standard Mockito can't handle these directly.

When Traditional Mocks Fail

Why do we need to mock these?

  • Static utility methods: Often used for common tasks, but hard to isolate if they have side effects or external dependencies.
  • Final classes/methods: Cannot be subclassed or overridden, making traditional proxy-based mocking impossible.
  • Constructor calls (new): Direct object creation within a method makes it hard to inject a mock instead.

Mocking these helps you isolate the code under test, even in complex or legacy systems.

Powering Up Mockito

To overcome these limitations, Mockito offers advanced capabilities through its inline mock maker, often referred to as mockito-inline.

This feature uses byte-code manipulation to "rewrite" classes at runtime, allowing Mockito to mock things it normally can't, such as static methods, final classes, and even constructors.

It's a powerful tool, but use it thoughtfully!

Setting Up Your Project

For Mockito versions 3.4.0 and newer, the inline mock maker is often enabled by default or available simply by using the standard mockito-core dependency.

If you're using an older version or encountering issues, you might need to explicitly declare mockito-inline. For most modern setups, just ensure you have a recent mockito-core version.

Here's how your build.gradle might look:

dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.0'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.0'
    testImplementation 'org.mockito:mockito-core:5.8.0' // Includes inline mock maker
    // For older Mockito versions, you might need:
    // testImplementation 'org.mockito:mockito-inline:5.8.0'
}

Mocking Static Helpers

To mock static methods, Mockito provides the MockedStatic interface. You create a mock context using Mockito.mockStatic() within a try-with-resources block. This ensures the mock is active only for the duration of that block.

Let's see an example with a utility class:

import org.mockito.Mockito;
import org.mockito.MockedStatic;

// Utility class with a static method
class MyStaticService {
    public static String getGreeting() {
        return "Hello from real static service!";
    }
    public static int add(int a, int b) {
        return a + b;
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println("Before mock: " + MyStaticService.getGreeting());

        // Mock the static method within a try-with-resources
        try (MockedStatic<MyStaticService> mockedStatic = Mockito.mockStatic(MyStaticService.class)) {
            mockedStatic.when(MyStaticService::getGreeting).thenReturn("Hello from mocked static!");
            mockedStatic.when(() -> MyStaticService.add(1, 2)).thenReturn(100);

            System.out.println("During mock (greeting): " + MyStaticService.getGreeting());
            System.out.println("During mock (add): " + MyStaticService.add(1, 2));
            System.out.println("During mock (add other): " + MyStaticService.add(5, 5)); // Not mocked, returns 0 by default

            // Verify calls (typically in a test assertion)
            mockedStatic.verify(MyStaticService::getGreeting);
            mockedStatic.verify(() -> MyStaticService.add(1, 2), Mockito.times(1));

        } // Mock is closed here

        System.out.println("After mock: " + MyStaticService.getGreeting());
    }
}

Checking Static Interactions

Just like with regular mocks, you can verify interactions with static methods. Use the verify() method on your MockedStatic object.

This ensures that your code under test called the static method as expected, with the correct arguments and number of times.

Notice the Mockito.times(1) in the previous example to check invocation count.

import org.mockito.Mockito;
import org.mockito.MockedStatic;

// Class from previous scene
class MyStaticService {
    public static String getGreeting() { return "Hello from real static!"; }
    public static int multiply(int a, int b) { return a * b; }
}

public class Main {
    public static void main(String[] args) {
        // Simulate a scenario where a static method is called
        System.out.println("Initial call: " + MyStaticService.multiply(2, 3));

        try (MockedStatic<MyStaticService> mockedStatic = Mockito.mockStatic(MyStaticService.class)) {
            mockedStatic.when(() -> MyStaticService.multiply(2, 3)).thenReturn(777);
            mockedStatic.when(() -> MyStaticService.multiply(5, 5)).thenReturn(123);

            // Call the static method through the mock
            System.out.println("Mocked call 1: " + MyStaticService.multiply(2, 3));
            System.out.println("Mocked call 2: " + MyStaticService.multiply(5, 5));

            // Verify specific calls
            mockedStatic.verify(() -> MyStaticService.multiply(2, 3), Mockito.times(1));
            mockedStatic.verify(() -> MyStaticService.multiply(5, 5), Mockito.atLeastOnce());
            // Verify that a different call was NOT made
            mockedStatic.verify(() -> MyStaticService.multiply(10, 10), Mockito.never());

            System.out.println("Verifications completed.");

        } // Mock is closed here
    }
}

Taming Final Classes

The mockito-inline mock maker also allows you to mock final classes and final methods. This is particularly useful when dealing with third-party libraries or legacy code where you can't easily change the class design.

You mock final classes just like regular classes: Mockito.mock(FinalClass.class).

import org.mockito.Mockito;

// A final class that cannot be extended normally
final class FinalProcessor {
    public final String process(String input) {
        return "Real processed: " + input.toUpperCase();
    }
    public int getVersion() {
        return 1;
    }
}

public class Main {
    public static void main(String[] args) {
        FinalProcessor realProcessor = new FinalProcessor();
        System.out.println("Real output: " + realProcessor.process("data"));
        System.out.println("Real version: " + realProcessor.getVersion());

        // Mocking a final class
        FinalProcessor mockProcessor = Mockito.mock(FinalProcessor.class);

        // Stubbing a final method
        Mockito.when(mockProcessor.process("data")).thenReturn("Mocked processed: data");
        Mockito.when(mockProcessor.getVersion()).thenReturn(99);

        System.out.println("Mocked output: " + mockProcessor.process("data"));
        System.out.println("Mocked version: " + mockProcessor.getVersion());

        // Verify interaction with the mocked final class
        Mockito.verify(mockProcessor).process("data");
    }
}

Intercepting New Objects

What if your code creates new objects directly using new MyObject()? MockedConstruction lets you intercept these calls and return mock instances instead of real ones.

This is crucial for testing classes that internally manage their dependencies rather than receiving them via dependency injection.

import org.mockito.Mockito;
import org.mockito.MockedConstruction;

// A class that creates another object internally
class DependentService {
    private final Helper helper;

    public DependentService() {
        this.helper = new Helper(); // Direct constructor call
    }

    public String doWork() {
        return "Service working with: " + helper.getGreeting();
    }
}

// The class whose constructor we want to mock
class Helper {
    public String getGreeting() {
        return "Real Helper greeting";
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println("Without mock:");
        DependentService realService = new DependentService();
        System.out.println(realService.doWork());

        System.out.println("\nWith mocked construction:");
        // Mock the constructor of Helper
        try (MockedConstruction<Helper> mockedConstruction = Mockito.mockConstruction(Helper.class,
                (mock, context) -> {
                    // This block runs every time new Helper() is called
                    Mockito.when(mock.getGreeting()).thenReturn("Mocked Helper greeting!");
                    System.out.println("Helper constructor intercepted!");
                })) {

            // When DependentService() is called, it calls new Helper(),
            // which now returns our stubbed mock.
            DependentService serviceWithMockedHelper = new DependentService();
            System.out.println(serviceWithMockedHelper.doWork());

            // You can get all constructed mocks
            Helper constructedMock = mockedConstruction.constructed().get(0);
            Mockito.verify(constructedMock).getGreeting(); // Verify interaction with the mock
            System.out.println("Verified mock interaction.");

        } // MockedConstruction is closed here
    }
}

Use With Care

While powerful, mocking statics and constructors can be an indicator of a design that's hard to test.

  • Design Smell: Heavily relying on these mocks might suggest your classes are too tightly coupled or not following dependency injection principles.
  • Readability: Tests using these advanced mocks can be harder to understand and maintain.
  • When to use: Best for legacy code, third-party libraries, or when refactoring isn't immediately feasible. Prioritize refactoring for testability when possible!

Advanced Mocking Check

Which of the following statements about mockito-inline and advanced mocking techniques are TRUE?

Advanced Mocking Recap

Great job! You've explored advanced Mockito techniques:

  • The mockito-inline mock maker enables mocking of statics, final classes, and constructors.
  • MockedStatic allows you to stub and verify static method calls within a defined scope.
  • MockedConstruction helps intercept and replace objects created via the new keyword.

These tools are powerful for testing challenging code, but remember to consider underlying design patterns. Next, you'll learn about Mockito best practices!

常见问题解答

「模拟静态方法与构造函数」课时是免费的吗?

是的 — 「模拟静态方法与构造函数」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Testing Mastery: JUnit, Mockito & Integration Tests 课程的其余内容,请升级到 CoddyKit PRO。 Testing Mastery: JUnit, Mockito & Integration Tests 课程共包含 4 节课。

「模拟静态方法与构造函数」这节课中我会学到什么?

探索使用 `mockito-inline` 等 Mockito 扩展模拟静态方法、最终类和构造函数的高级技术。 你通过在浏览器中直接运行的动手代码来练习 Testing Mastery: JUnit, Mockito & Integration Tests,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Testing Mastery: JUnit, Mockito & Integration Tests 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Testing Mastery: JUnit, Mockito & Integration Tests 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。

「模拟静态方法与构造函数」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Testing Mastery: JUnit, Mockito & Integration Tests 课中编写并运行代码吗?

能。每节 Testing Mastery: JUnit, Mockito & Integration Tests 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 自定义答案与回调
  2. 模拟静态方法与构造函数
  3. Mockito 最佳实践
  4. 使用 ArgumentCaptor 捕获参数
← 返回 Testing Mastery: JUnit, Mockito & Integration Tests