java · 2024-06-30 0

@Mock @Spy @InjectMocks 区别及使用

一、区别

1.说明

  • @Mock 模拟对象的所有方法都不会调用其实际实现,而是返回默认值或根据定义的行为返回值,相当于 Mockito.mock

  • @Spy 间谍对象是一个真实的对象,但你可以选择性地模拟其中的一些方法,相当于 Mockito.spy

  • @InjectMocks 用于创建一个主要的对象(通常是要测试的对象),并且自动注入所有使用 @Mock 或 @Spy 注解的依赖项

    2.模拟

  • 模拟有返回值的方法:Mockito.when(methodCall).thenReturn(value)

  • 模拟无返回值的方法:Mockito.doNothing().when(mock).method()

  • 模拟有返回值的静态方法:mockedStatic.when(verification).thenReturn(value)

  • 模拟无返回值的静态方法:mockedStatic.when(verification).thenAnswer(invocation -> null)

    二、示例

1.pom

<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-core</artifactId>
        <version>4.5.1</version>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-inline</artifactId>
        <version>4.5.1</version>
        <scope>test</scope>
    </dependency>
</dependencies>

2.代码

MyDao1.java

 public interface MyDao1 {

    Integer count11(Integer i);

    Integer count12(Integer i);

    void test11();
}

public class MyDao1Impl implements MyDao1 {

    @Override
    public Integer count11(Integer i) {
        return i;
    }

    @Override
    public Integer count12(Integer i) {
        return i;
    }

    @Override
    public void test11() {
    }
}

MyDao2.java

public interface MyDao2 {

    Integer count21(Integer i);

    Integer count22(Integer i);
}

public class MyDao2Impl implements MyDao2 {

    @Override
    public Integer count21(Integer i) {
        return i;
    }

    @Override
    public Integer count22(Integer i) {
        return i;
    }
}

MyServiceImpl.java

public interface MyService {

    Integer count();
}

public class MyServiceImpl implements MyService {

    private MyDao1 myDao1;

    private MyDao2 myDao2;

    private static int V = 10;

    public MyServiceImpl(MyDao1 myDao1, MyDao2 myDao2) {
        this.myDao1 = myDao1;
        this.myDao2 = myDao2;
    }

    @Override
    public Integer count() {
        Integer result1 = myDao1.count11(10);
        Integer result2 = myDao2.count21(10);
        Integer result3 = value(10);
        increaseV(10);
        return result1 + result2 + result3 + V;
    }

    public static Integer value(Integer i) {
        return i;
    }

    public static void increaseV(Integer i) {
        V = V + i;
    }
}

3.测试类

import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.*;

import java.lang.reflect.Field;

public class MyServiceImplTest {

    // 用于创建一个主要的对象(通常是要测试的对象),并且自动注入所有使用 @Mock 或 @Spy 注解的依赖项
    // 只能能标注在类上,标志在接口上会报错
    @InjectMocks
    private MyServiceImpl myService;

    // 可标注在接口上和类上
    // 如果是类,值相当于会实例化,如果是接口,会生成一个实现类
    // 模拟对象的所有方法都不会调用其实际实现,而是返回默认值或根据定义的行为返回值
    // 相当于 MyDao1Impl myDao1 = Mockito.mock(MyDao1Impl.class)
    @Mock
    // private MyDao1 myDao1;
    private MyDao1Impl myDao1;

    // 可标注在接口上和类上
    // 间谍对象是一个真实的对象,但你可以选择性地模拟其中的一些方法
    // 相当于 MyDao2Impl myDao2 = Mockito.spy(MyDao2Impl.class);
    @Spy
    // private MyDao2 myDao2;
    private MyDao2Impl myDao2;

    private AutoCloseable openMocks;

    private MockedStatic mockedStatic;

    @Before
    public void before() {
        // 初始化模拟对象
        // 在 Mockito 2.x 中,通常使用 @RunWith(MockitoJUnitRunner.class) 或者 @ExtendWith(MockitoExtension.class) 来自动初始化模拟对象,
        // 在 Mockito 3 中,推荐的做法是使用 MockitoAnnotations.openMocks(this);。
        // 如果使用的是 JUnit 5,可以考虑使用 @ExtendWith(MockitoExtension.class),这会自动调用 openMocks(this),因此你无需在 @BeforeEach 方法中显式调用它。但是,在某些情况下,你可能仍然需要显式调用 openMocks(this),比如在静态上下文中或当需要更细粒度的控制时。
        openMocks = MockitoAnnotations.openMocks(this);

        // 模拟有返回值的方法
        Mockito.when(myDao1.count11(Mockito.any())).thenReturn(100);
        Mockito.when(myDao2.count21(Mockito.any())).thenReturn(100);
        // 模拟无返回值的方法
        Mockito.doNothing().when(myDao1).test11();

        // Mockito 3.4.0 版本之后增加了对 Static 方法的支持
        // 模拟有返回值的静态方法
        // 这个方法会 mock 指定类的所有 static 方法,会返回默认值或根据定义的行为返回值,像所有静态方法上,加上了 @mock
        mockedStatic = Mockito.mockStatic(MyServiceImpl.class);
        // 模拟无返回值的静态方法
        // Mockito.when(MyServiceImpl.value(Mockito.any())).thenReturn(100);
        mockedStatic.when(() -> MyServiceImpl.value(Mockito.any())).thenReturn(100);
        // 模拟静态方法无返回值
        mockedStatic.when(() -> MyServiceImpl.increaseV(Mockito.any())).thenAnswer(invocation -> {
            // 修改私有静态变量 V 的值
            Field field = MyServiceImpl.class.getDeclaredField("V");
            field.setAccessible(true);
            field.setInt(null, 100);
            return null;
        });
    }

    @Test
    public void testCount1() {
        Integer i11 = myDao1.count11(1); // 使用过 Mockito.when,返回 100
        Integer i12 = myDao1.count12(1); // @Mock myDao1,会被 mock,返回默认值 0

        Integer i21 = myDao2.count21(1); // 使用过 Mockito.when,返回 100
        Integer i22 = myDao2.count22(1); // @Spy myDao2,不会被 mock

        Assert.assertEquals(Integer.valueOf(100), i11);
        Assert.assertEquals(Integer.valueOf(0), i12);
        Assert.assertEquals(Integer.valueOf(100), i21);
        Assert.assertEquals(Integer.valueOf(1), i22);

        Integer count = myService.count();
        Assert.assertEquals(Integer.valueOf(400), count);
    }

    @Test
    public void testCount2() {
        Integer count = myService.count();
        Integer expected = 400;
        Assert.assertEquals(expected, count);
    }

    @After
    public void after() throws Exception {
        if (openMocks != null) {
            openMocks.close();
        }
        if (mockedStatic != null) {
            mockedStatic.close();
        }
    }
}

4.Mockito.when、Mockito.doReturn、Mockito.doAnswer、Mockito.doNothing、Mockito.mockStatic、Mockito.verify

4.1

参数:
Mockito.when(methodCall).thenReturn(value)
Mockito.when(methodCall).then(answer);
Mockito.when(methodCall).thenAnswer(answer);
Mockito.doReturn(toBeReturned).when(mock);
Mockito.doAnswer(answer).when(mock);

在使用了Spies对象(@Spy注解)的情况下他们调用的结果是不相同的
when(...) thenReturn(...) 会调用真实的方法;
doReturn(...) when(...) 不会调用真实方法。

// 模拟有返回值的方法
Mockito.when(myDao1.count11(Mockito.any())).thenReturn(100);

Mockito.when(myDao1.count11(Mockito.any())).then(invocation -> {
    Object[] arguments = invocation.getArguments();
    Method method = invocation.getMethod();
    return 100;
});

Mockito.when(myDao1.count11(Mockito.any())).thenAnswer(invocation -> {
    System.out.println("- - -");
    Object[] arguments = invocation.getArguments();
    Method method = invocation.getMethod();
    return 100;
});

Mockito.doReturn(100).when(myDao1).count11(Mockito.any());

Mockito.doAnswer(invocation -> {
    Object[] arguments = invocation.getArguments();
    Method method = invocation.getMethod();
    return 100;
}).when(myDao1).count11(Mockito.any());

4.2

参数:
Mockito.doNothing().when(mock);

Mockito.doNothing().when(myDao1).test11();

4.3

参数:
Mockito.mockStatic(classToMock).thenReturn(verification);
Mockito.mockStatic(classToMock).when(verification);
Mockito.mockStatic(classToMock).thenAnswer(verification);

MockedStatic mockedStatic = Mockito.mockStatic(MyServiceImpl.class);

mockedStatic.when(() -> MyServiceImpl.value(Mockito.any())).thenReturn(100);

mockedStatic.when(() -> MyServiceImpl.value(Mockito.any())).then(invocation -> {
    Object[] arguments = invocation.getArguments();
    Method method = invocation.getMethod();
    return 100;
});

mockedStatic.when(() -> MyServiceImpl.value(Mockito.any())).thenAnswer(invocation -> {
    Object[] arguments = invocation.getArguments();
    Method method = invocation.getMethod();
    return 100;
});

4.4

参数:
Mockito.verify(mock);
Mockito.verify(mock, mode);

// 验证方法调用
List mockList = Mockito.mock(List.class);
mockList.add("aa");
mockList.add("bb");
mockList.add("bb");
Mockito.verify(mockList).add("aa");
Mockito.verify(mockList, Mockito.times(2)).add("bb");