Mockito:Java单元测试Mock框架

文章目录

一、写在前面

1、简介

参考资料:https://www.baeldung-cn.com/mockito-series

在进行单元测试时,如果依赖的服务尚未开发完成,或依赖的对象不方便构造,这时我们就需要模拟( Mock)对象。

2、依赖

如果是普通java项目,需要引入mockito-core,对于 Spring Boot 用户,spring-boot-starter-test 中已经集成好了Mockito,无需配置。

xml 复制代码
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>3.12.4</version>
    <scope>test</scope>
</dependency>
xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
</dependency>

二、使用

1、基本使用

java 复制代码
import static org.mockito.Mockito.*;

// 创建mock对象 
// 你可以mock具体的类型,不仅只是接口
List mockedList = mock(List.class);
// 对于高版本Mockito 4.10.0+,可以写的更简洁
// List mockedList = mock();

// 下面添加测试桩(stubbing),指定mock的行为
// "当" 调用 mockedList.get(0) 返回 "first"
when(mockedList.get(0)).thenReturn("first");

// 下面代码将打印 "first"
System.out.println(mockedList.get(0));

// 下面将打印 "null",因为 get(999) 没有被打桩
System.out.println(mockedList.get(999));

2、注解

(1)开启注解

开启注解有三种方式:

java 复制代码
//方法一:在JUnit 上设置 MockitoJUnitRunner
@ExtendWith(MockitoExtension.class)
public class MockitoAnnotationUnitTest {
    ...
}

// 方法二:手动编码,调用 MockitoAnnotations.openMocks() 方法
@Before
public void init() {
    MockitoAnnotations.openMocks(this);
}

//最后, 我们可以使用 MockitoJUnit.rule():
public class MockitoAnnotationsInitWithMockitoJUnitRuleUnitTest {
	//注意,这需要将rule 设置为 public
    @Rule
    public MockitoRule initRule = MockitoJUnit.rule();
    ...
}

(2)@Mock 注解

@Mock 是 Mockito 中用的最多的注解,我们用它来创建并注入mock对象,而不用手动调用 Mockito.mock 方法。

java 复制代码
@Test
public void whenNotUseMockAnnotation_thenCorrect() {
    List mockList = Mockito.mock(ArrayList.class);

    mockList.add("one");
    Mockito.verify(mockList).add("one");
    assertEquals(0, mockList.size());

    Mockito.when(mockList.size()).thenReturn(100);
    assertEquals(100, mockList.size());
}

对比一下,@Mock 注解可以完成以上编码的工作。

java 复制代码
@Mock
List<String> mockedList;

@Test
public void whenUseMockAnnotation_thenMockIsInjected() {
    mockedList.add("one");
    Mockito.verify(mockedList).add("one");
    assertEquals(0, mockedList.size());

    Mockito.when(mockedList.size()).thenReturn(100);
    assertEquals(100, mockedList.size());
}

(3)@DoNotMock 注解

@DoNotMock 注解用来标记不要mock的类或接口

java 复制代码
import org.mockito.exceptions.misusing.DoNotMock;

@DoNotMock(reason = "Use a real instance instead")
public abstract class NotToMock {
    // Class implementation
}

(4)@Spy 注解

spy与mock的区别是,mock代理了目标对象的全部方法,spy只是部分代理

我们先不用注解的方式,演示如何创建一个 spy List。

java 复制代码
@Test
public void whenNotUseSpyAnnotation_thenCorrect() {
	// 需要声明一个对象
    List<String> spyList = Mockito.spy(new ArrayList<String>());
	// 走正常的ArrayList方法
    spyList.add("one");
    spyList.add("two");

    Mockito.verify(spyList).add("one");
    Mockito.verify(spyList).add("two");

    assertEquals(2, spyList.size());

    Mockito.doReturn(100).when(spyList).size();
    assertEquals(100, spyList.size());
}

然后我们通过 @Spy 注解的方式完成相同的工作:

java 复制代码
@Spy
List<String> spiedList = new ArrayList<String>();

@Test
public void whenUseSpyAnnotation_thenSpyIsInjectedCorrectly() {
    spiedList.add("one");
    spiedList.add("two");

    Mockito.verify(spiedList).add("one");
    Mockito.verify(spiedList).add("two");

    assertEquals(2, spiedList.size());

    Mockito.doReturn(100).when(spiedList).size();
    assertEquals(100, spiedList.size());
}

(5)@Captor 注解

ArgumentCaptor 让我们能够 "拦截" 方法调用的参数,从而对其进行验证,这在测试依赖于外部交互的代码时非常有用。

接下来让我们看看如何使用 @Captor 注解创建 ArgumentCaptor 实例。

在下面的示例中,我们先不使用 @Captor 注解,手动创建一个 ArgumentCaptor:

java 复制代码
@Test
public void whenUseCaptorAnnotation_thenTheSame() {
    List mockList = Mockito.mock(List.class);
    ArgumentCaptor<String> arg = ArgumentCaptor.forClass(String.class);

    mockList.add("one");
    Mockito.verify(mockList).add(arg.capture());

    assertEquals("one", arg.getValue());
}

现在,让我们使用 @Captor 注解来创建 ArgumentCaptor:

java 复制代码
@Mock
List mockedList;

@Captor 
ArgumentCaptor argCaptor;

@Test
public void whenUseCaptorAnnotation_thenTheSam() {
    mockedList.add("one");
    Mockito.verify(mockedList).add(argCaptor.capture());

    assertEquals("one", argCaptor.getValue());
}

(6)@InjectMocks 注解

现在我们来讨论如何使用 @InjectMocks 注解将mock字段自动注入到被测试对象中。

在下面的示例中,我们将使用 @InjectMocks 把mock的 wordMap 注入到 MyDictionary dic 中:

java 复制代码
@Mock
Map<String, String> wordMap;

@InjectMocks
MyDictionary dic = new MyDictionary();

@Test
public void whenUseInjectMocksAnnotation_thenCorrect() {
    Mockito.when(wordMap.get("aWord")).thenReturn("aMeaning");

    assertEquals("aMeaning", dic.getMeaning("aWord"));
}

下面是 MyDictionary 类:

java 复制代码
public class MyDictionary {
    Map<String, String> wordMap;

    public MyDictionary() {
        wordMap = new HashMap<String, String>();
    }
    public void add(final String word, final String meaning) {
        wordMap.put(word, meaning);
    }
    public String getMeaning(final String word) {
        return wordMap.get(word);
    }
}

(7)将Mock注入Spy中

与前面测试类似,我们可能想在spy中注入一个mock:

java 复制代码
@Mock
Map<String, String> wordMap;

@Spy
MyDictionary spyDic = new MyDictionary();

然而,Mockito 并不支持将mock注入spy,因此下面的测试会出现异常:

java 复制代码
@Test 
public void whenUseInjectMocksAnnotation_thenCorrect() { 
    Mockito.when(wordMap.get("aWord")).thenReturn("aMeaning"); 

    assertEquals("aMeaning", spyDic.getMeaning("aWord")); 
}

如果我们想在 spy 中使用 mock,可以通过构造函数手动注入 mock:

java 复制代码
MyDictionary(Map<String, String> wordMap) {
    this.wordMap = wordMap;
}

现在需要我们手动创建spy,而不使用注释:

java 复制代码
@Mock
Map<String, String> wordMap; 

MyDictionary spyDic;

@BeforeEach
public void init() {
    MockitoAnnotations.openMocks(this);
    spyDic = Mockito.spy(new MyDictionary(wordMap));
}

现在测试将通过。

(8)使用注解时遇到空指针

通常,当我们使用 @Mock 或 @Spy 注解时,可能会遇到 NullPointerException 异常:

java 复制代码
public class MockitoAnnotationsUninitializedUnitTest {

    @Mock
    List<String> mockedList;

    @Test(expected = NullPointerException.class)
    public void whenMockitoAnnotationsUninitialized_thenNPEThrown() {
        Mockito.when(mockedList.size()).thenReturn(1);
    }
}

大多数情况下,是因为我们没有启用 Mockito 注解。所以请查看我们第一节的内容,使用Mockito前别忘了先初始化。

3、Mockito模拟抛出异常

测试类:

java 复制代码
class MyDictionary {
    
    private Map<String, String> wordMap;

    public void add(String word, String meaning) {
        wordMap.put(word, meaning);
    }

    public String getMeaning(String word) {
        return wordMap.get(word);
    }
}

(1)非Void返回值

首先,如果方法的返回类型不是void,我们可以使用when().thenThrow():

java 复制代码
@Test
void givenNonVoidReturnType_whenUsingWhenThen_thenExceptionIsThrown() {
    MyDictionary dictMock = mock(MyDictionary.class);
    when(dictMock.getMeaning(anyString())).thenThrow(NullPointerException.class);
    
    assertThrows(NullPointerException.class, () -> dictMock.getMeaning("word"));
}

请注意,我们已配置了返回类型为String的getMeaning()方法,使其在被调用时抛出NullPointerException。

(2)Void返回值

如果我们的方法返回void,我们将使用doThrow():

java 复制代码
@Test
void givenVoidReturnType_whenUsingDoThrow_thenExceptionIsThrown() {
    MyDictionary dictMock = mock(MyDictionary.class);
    doThrow(IllegalStateException.class).when(dictMock)
        .add(anyString(), anyString());
    
    assertThrows(IllegalStateException.class, () -> dictMock.add("word", "meaning"));
}

在这里,我们配置了一个返回void的add()方法,在调用时抛出IllegalStateException。

对于void返回类型,我们不能使用when().thenThrow(),因为编译器不允许在括号内使用void方法。

(3)异常作为对象

为了配置异常本身,我们可以像之前示例那样传递异常的类,也可以作为对象:

java 复制代码
@Test
void givenNonVoidReturnType_whenUsingWhenThenAndExeceptionAsNewObject_thenExceptionIsThrown() {
    MyDictionary dictMock = mock(MyDictionary.class);
    when(dictMock.getMeaning(anyString())).thenThrow(new NullPointerException("Error occurred"));
    
    assertThrows(NullPointerException.class, () -> dictMock.getMeaning("word"));
}

对于doThrow(),我们也可以这样做:

java 复制代码
@Test
void givenNonVoidReturnType_whenUsingDoThrowAndExeceptionAsNewObject_thenExceptionIsThrown() {
    MyDictionary dictMock = mock(MyDictionary.class);
    doThrow(new IllegalStateException("Error occurred")).when(dictMock)
        .add(anyString(), anyString());
    
    assertThrows(IllegalStateException.class, () -> dictMock.add("word", "meaning"));
}

(4)模拟对象(spy)

我们还可以以与mock相同的方式为模拟对象(Spy)配置抛出异常:

java 复制代码
@Test
void givenSpyAndNonVoidReturnType_whenUsingWhenThen_thenExceptionIsThrown() {
    MyDictionary dict = new MyDictionary();
    MyDictionary spy = Mockito.spy(dict);
    when(spy.getMeaning(anyString())).thenThrow(NullPointerException.class);
    
    assertThrows(NullPointerException.class, () -> spy.getMeaning("word"));
}

4、When/Then 用法

测试类:

java 复制代码
public class MyList extends AbstractList<String> {

    @Override
    public String get(final int index) {
        return null;
    }
    @Override
    public int size() {
        return 1;
    }
}

为mock配置简单返回行为:

java 复制代码
MyList listMock = mock(MyList.class);
when(listMock.add(anyString())).thenReturn(false);

boolean added = listMock.add(randomAlphabetic(6));
assertThat(added).isFalse();

以另一种方式为mock配置返回行为:

java 复制代码
MyList listMock = mock(MyList.class);
doReturn(false).when(listMock).add(anyString());

boolean added = listMock.add(randomAlphabetic(6));
assertThat(added).isFalse();

配置mock在方法调用时抛出异常:

java 复制代码
MyList listMock = mock(MyList.class);
when(listMock.add(anyString())).thenThrow(IllegalStateException.class);

assertThrows(IllegalStateException.class, () -> listMock.add(randomAlphabetic(6)));

配置具有void返回类型的方法的行为------抛出异常:

java 复制代码
MyList listMock = mock(MyList.class);
doThrow(NullPointerException.class).when(listMock).clear();

assertThrows(NullPointerException.class, () -> listMock.clear());

配置多次调用的行为:

java 复制代码
MyList listMock = mock(MyList.class);
when(listMock.add(anyString()))
  .thenReturn(false)
  .thenThrow(IllegalStateException.class);

assertThrows(IllegalStateException.class, () -> {
    listMock.add(randomAlphabetic(6));
    listMock.add(randomAlphabetic(6));
});

配置spy的行为:

java 复制代码
MyList instance = new MyList();
MyList spy = spy(instance);

doThrow(NullPointerException.class).when(spy).size();

assertThrows(NullPointerException.class, () -> spy.size());

配置mock调用实际底层方法的行为:

java 复制代码
MyList listMock = mock(MyList.class);
when(listMock.size()).thenCallRealMethod();

assertThat(listMock).hasSize(1);

配置mock方法调用自定义Answer:

java 复制代码
MyList listMock = mock(MyList.class);
doAnswer(invocation -> "Always the same").when(listMock).get(anyInt());

String element = listMock.get(1);
assertThat(element).isEqualTo("Always the same");

5、Mockito Verify用法

在 Mockito verify 用于验证某个方法是否被调用,以及调用的次数和参数。本文我们 通过示例演示 Mockito verify 的各种用法。

下面是我们将要 mock 的 List:

java 复制代码
public class MyList extends AbstractList<String> {

    @Override
    public String get(final int index) {
        return null;
    }

    @Override
    public int size() {
        return 1;
    }
}

简单验证:

java 复制代码
List<String> mockedList = mock(MyList.class);
mockedList.size();

// 验证 size() 方法是否被调用
verify(mockedList).size();

验证mock的调用次数:

java 复制代码
List<String> mockedList = mock(MyList.class);
mockedList.size();
// 验证 size() 方法是否被调用了 1 次
verify(mockedList, times(1)).size();

验证 mock 对象的所有方法都没有被调用:

java 复制代码
List<String> mockedList = mock(MyList.class);
verifyNoInteractions(mockedList);

验证 mock 的某个方法被调用:

java 复制代码
List<String> mockedList = mock(MyList.class);
verify(mockedList, times(0)).size();

验证没有额外的调用:

java 复制代码
List<String> mockedList = mock(MyList.class);
mockedList.size();
mockedList.clear();

verify(mockedList).size();
// 除了 size() 外,clear()也被调用了,所以下面会抛出异常
assertThrows(NoInteractionsWanted.class, () -> verifyNoMoreInteractions(mockedList));

验证调用顺序:

java 复制代码
List<String> mockedList = mock(MyList.class);
mockedList.size();
mockedList.add("a parameter");
mockedList.clear();

InOrder inOrder = Mockito.inOrder(mockedList);
inOrder.verify(mockedList).size();
inOrder.verify(mockedList).add("a parameter");
inOrder.verify(mockedList).clear();

验证没有调用某个方法:

java 复制代码
List<String> mockedList = mock(MyList.class);
mockedList.size();

verify(mockedList, never()).clear();

验证至少调用次数:

java 复制代码
List<String> mockedList = mock(MyList.class);
mockedList.clear();
mockedList.clear();
mockedList.clear();

verify(mockedList, atLeast(1)).clear();
verify(mockedList, atMost(10)).clear();

验证调用时实际传入的参数:

java 复制代码
List<String> mockedList = mock(MyList.class);
mockedList.add("test");

verify(mockedList).add("test");

验证调用时传入的参数,不关心具体值:

java 复制代码
List<String> mockedList = mock(MyList.class);
mockedList.add("test");

verify(mockedList).add(anyString());

验证调用时传入的参数,并捕获参数:

java 复制代码
List<String> mockedList = mock(MyList.class);
mockedList.addAll(Lists.<String> newArrayList("someElement"));

ArgumentCaptor<List<String>> argumentCaptor = ArgumentCaptor.forClass(List.class);
verify(mockedList).addAll(argumentCaptor.capture());

List<String> capturedArgument = argumentCaptor.getValue();
assertThat(capturedArgument).contains("someElement");

6、Mockito模拟返回值为void的方法

测试类:

java 复制代码
public class MyList extends AbstractList<String> {
 
    @Override
    public void add(int index, String element) {
        // no-op
    }
}

(1)简单的模拟与验证

void方法可以与Mockito的doNothing()、doThrow()和doAnswer()方法一起使用,使模拟和验证变得直观:

java 复制代码
@Test
public void whenAddCalled_thenVerified() {
    MyList myList = mock(MyList.class);
    doNothing().when(myList).add(isA(Integer.class), isA(String.class));
    myList.add(0, "");
 
    verify(myList, times(1)).add(0, "");
}

然而,doNothing()是Mockito对void方法的默认行为。

这个版本的whenAddCalledVerified()与上面的实现相同:

java 复制代码
@Test
void whenAddCalled_thenVerified() {
    MyList myList = mock(MyList.class);
    myList.add(0, "");
 
    verify(myList, times(1)).add(0, "");
}

doThrow()会抛出一个异常:

java 复制代码
@Test
void givenNull_whenAddCalled_thenThrowsException() {
    MyList myList = mock(MyList.class);
    
    assertThrows(Exception.class, () -> {
    doThrow().when(myList).add(isA(Integer.class), isNull());
    });

    myList.add(0, null);
}

我们将在下面讨论doAnswer()。

(2)参数捕获

使用doNothing()覆盖默认行为的一个原因是捕获参数。

在上述例子中,我们使用verify()方法检查传递给add()的方法参数。

然而,我们可能需要捕获参数并对其做更多的处理。

在这种情况下,我们像上面那样使用doNothing(),但使用一个ArgumentCaptor:

java 复制代码
@Test
void givenArgumentCaptor_whenAddCalled_thenValueCaptured() {
    MyList myList = mock(MyList.class);
    
    ArgumentCaptor<String> valueCapture = ArgumentCaptor.forClass(String.class);
    doNothing().when(myList).add(any(Integer.class), valueCapture.capture());
    
    myList.add(0, "captured");
    
    assertEquals("captured", valueCapture.getValue());
}

(3)回答对void的调用

一个方法可能执行的不仅仅是添加或设置值的简单行为。

对于这些情况,我们可以使用Mockito的Answer来添加我们需要的行为:

java 复制代码
@Test
void givenDoAnswer_whenAddCalled_thenAnswered() {
    MyList myList = mock(MyList.class);
    
    doAnswer(invocation -> {
        Object arg0 = invocation.getArgument(0);
        Object arg1 = invocation.getArgument(1);
        
        assertEquals(3, arg0);
        assertEquals("answer me", arg1);
        return null;
    }).when(myList).add(any(Integer.class), any(String.class));
    
    myList.add(3, "answer me");
}

如Mockito的Java 8特性所述,我们使用带有Answer的lambda来为add()定义自定义行为。

(4)部分模拟

部分模拟也是一个选择。Mockito的doCallRealMethod()也可以用于void方法:

java 复制代码
@Test
void givenDoCallRealMethod_whenAddCalled_thenRealMethodCalled() {
    MyList myList = mock(MyList.class);
    
    doCallRealMethod().when(myList).add(any(Integer.class), any(String.class));
    myList.add(1, "real");
    
    verify(myList, times(1)).add(1, "real");
}

这样,我们可以在同时调用实际方法并进行验证。

7、Mockito模拟final类和方法

在早期版本的 Mockito(3.0 之前),默认是不支持模拟 final 类、final 方法和静态方法的,因为 Java 的 final 修饰符会限制字节码修改,而 Mockito 传统上依赖 CGLIB 或 Javassist 等库通过生成子类的方式创建 mock 对象,final 元素会阻止这种子类生成。

但从 Mockito 3.0 版本开始,通过结合 Byte Buddy 字节码操作库和 Java Agent 技术,实现了对 final 类、final 方法及静态方法的模拟支持。

测试类:

java 复制代码
public class MyList extends AbstractList<String> {
    final public int finalMethod() {
        return 0;
    }
}
public final class FinalList extends MyList {

    @Override
    public int size() {
        return 1;
    }
}

用法没有什么不同,

java 复制代码
@Test
public void whenMockFinalMethod_thenMockWorks() {

    MyList myList = new MyList();

    MyList mock = mock(MyList.class);
    when(mock.finalMethod()).thenReturn(1);

    assertThat(mock.finalMethod()).isNotZero();
}
@Test
public void whenMockFinalClass_thenMockWorks() {

    FinalList mock = mock(FinalList.class);
    when(mock.size()).thenReturn(2);

    assertThat(mock.size()).isNotEqualTo(1);
}

9、Mockito模拟静态方法

Mockito 3.4.0版本之前,是不支持直接mock静态方法,需要借助于PowerMockito。

有人可能会说,在编写整洁(Clean Code)的面向对象 代码时,我们不应该需要模拟静态类。这通常暗示了我们的应用存在设计问题。

为什么呢?首先,依赖于静态方法的类具有紧密的耦合性,其次,它几乎总是导致难以测试的代码。理想情况下,一个类不应当负责获取其依赖项,如果可能的话,它们应该由外部注入。

测试类:

java 复制代码
public class StaticUtils {

    private StaticUtils() {}

    public static List<Integer> range(int start, int end) {
        return IntStream.range(start, end)
          .boxed()
          .collect(Collectors.toList());
    }

    public static String name() {
        return "Baeldung";
    }
}

(1)Mock 无参静态方法

Mockito 3.4.0版本后,我们可以使用 Mockito.mockStatic(Class classToMock) 来mock静态方法的调用。 其返回值是一个MockedStatic类型的模拟对象。

注意返回的是一个 scoped mock object,它只在当前线程(thread-local)作用域内有效,用完需要close模拟对象,这就是为什么我们使用 try-with-resources,MockedStatic 继承了 AutoCloseable接口。

java 复制代码
@Test
void givenStaticMethodWithNoArgs_whenMocked_thenReturnsMockSuccessfully() {
    assertThat(StaticUtils.name()).isEqualTo("Baeldung");

    try (MockedStatic<StaticUtils> utilities = Mockito.mockStatic(StaticUtils.class)) {
        // 模拟 StaticUtils.name()方法
        utilities.when(StaticUtils::name).thenReturn("Eugen");
        assertThat(StaticUtils.name()).isEqualTo("Eugen");
    }

    // 离开mock作用域后调用的是真实的方法
    assertThat(StaticUtils.name()).isEqualTo("Baeldung");
}

(2)mock带有参数的静态方法

用法和无参静态方法类似,除了需要指定模拟的参数。

java 复制代码
@Test
void givenStaticMethodWithArgs_whenMocked_thenReturnsMockSuccessfully() {
    assertThat(StaticUtils.range(2, 6)).containsExactly(2, 3, 4, 5);

    try (MockedStatic<StaticUtils> utilities = Mockito.mockStatic(StaticUtils.class)) {
        // mock `StaticUtils.range()` 方法,它有2个参数:
        utilities.when(() -> StaticUtils.range(2, 6))
          .thenReturn(Arrays.asList(10, 11, 12));

        assertThat(StaticUtils.range(2, 6)).containsExactly(10, 11, 12);
    }

    assertThat(StaticUtils.range(2, 6)).containsExactly(2, 3, 4, 5);
}

(3)解决MockitoException:Deregistering Existing Mock Registrations

在Java中,当试图在同一线程上下文中注册多个静态模拟时,通常会出现 "静态模拟已经在当前线程中注册" 的异常,违反了单次注册约束。 要解决这个问题,我们必须在创建新模拟之前先注销现有的静态模拟。

简单来说,我们需要:

在每个线程中为静态模拟注册一次,最好使用如@Before这样的设置方法。

在注册之前检查mock是否已经注册以防止冗余。

在使用@After注册新的静态模拟之前,请先取消注册同一类的所有现有模拟。

以下是如何处理 "static mocking is already registered in the current thread" 异常的完整示例:

java 复制代码
public class StaticMockRegistrationUnitTest {

    private MockedStatic<StaticUtils> mockStatic;

    @Before
    public void setUp() {
        // Registering a static mock for UserService before each test
        mockStatic = mockStatic(StaticUtils.class);
    }

    @After
    public void tearDown() {
        // Closing the mockStatic after each test
        mockStatic.close();
    }

    @Test
    public void givenStaticMockRegistration_whenMocked_thenReturnsMockSuccessfully() {
        // Ensure that the static mock for UserService is registered
        assertTrue(Mockito.mockingDetails(StaticUtils.class).isMock());
    }

    @Test
    public void givenAnotherStaticMockRegistration_whenMocked_thenReturnsMockSuccessfully() {
        // Ensure that the static mock for UserService is registered
        assertTrue(Mockito.mockingDetails(StaticUtils.class).isMock());
    }
}

在上述示例中,带有 @Before 注解的 setUp() 方法会在每个测试用例之前执行,确保一致的测试环境。在这个方法中,使用 mockStatic(StaticUtils.class) 为 StaticUtils 注册静态模拟。这个注册过程确保每个测试前都会实例化一个新的静态模拟,保持测试的独立性,防止测试用例之间相互干扰。

相反,@After 注解的 tearDown() 方法会在每个测试用例后执行,释放测试执行期间获取的所有资源。

这个细致的设置和清理流程确保每个测试用例在其控制的环境中运行,促进可靠和可重现的测试结果,同时遵循单元测试的最佳实践。

三、Spring中使用Mockito

1、Spring中使用Mockito案例

首先,我们必须配置测试的应用上下文:

java 复制代码
@Profile("test")
@Configuration
public class NameServiceTestConfiguration {
    @Bean
    @Primary
    public NameService nameService() {
        return Mockito.mock(NameService.class);
    }
}

@Profile注解告诉Spring只有在"test"配置活跃时才应用此配置。@Primary注解确保在自动装配时使用这个实例而不是真实实例。方法本身创建并返回我们的NameService类的Mockito模拟。

接下来我们可以编写单元测试:

java 复制代码
@ActiveProfiles("test")
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = MocksApplication.class)
public class UserServiceUnitTest {

    @Autowired
    private UserService userService;

    @Autowired
    private NameService nameService;

    @Test
    public void whenUserIdIsProvided_thenRetrievedNameIsCorrect() {
        Mockito.when(nameService.getUserName("SomeId")).thenReturn("Mock user name");
        String testName = userService.getUserName("SomeId");
        Assert.assertEquals("Mock user name", testName);
    }
}

我们使用@ActiveProfiles注解启用"test"配置,并激活我们之前编写的模拟配置。结果,Spring为UserService类自动装配一个真实实例,但对于NameService类则是模拟对象。测试本身是一个典型的JUnit+Mockito测试。我们配置模拟对象的行为,然后调用我们想要测试的方法,并断言其返回我们期望的值。

也可以(尽管不推荐)避免在这样的测试中使用环境配置。要做到这一点,可以移除@Profile和@ActiveProfiles注解,并在UserServiceTest类上添加@ContextConfiguration(classes = NameServiceTestConfiguration.class)注解。

相关推荐
_oP_i3 小时前
RabbitMQ 队列配置设置 RabbitMQ 消息监听器的并发消费者数量java
java·rabbitmq·java-rabbitmq
Monkey-旭3 小时前
Android Bitmap 完全指南:从基础到高级优化
android·java·人工智能·计算机视觉·kotlin·位图·bitmap
我爱996!3 小时前
SpringMVC——响应
java·服务器·前端
小宋10213 小时前
多线程向设备发送数据
java·spring·多线程
天若有情6734 小时前
【python】Python爬虫入门教程:使用requests库
开发语言·爬虫·python·网络爬虫·request
大佐不会说日语~4 小时前
Redis高频问题全解析
java·数据库·redis
寒水馨4 小时前
Java 17 新特性解析与代码示例
java·开发语言·jdk17·新特性·java17
启山智软4 小时前
选用Java开发商城的优势
java·开发语言
秃然想通4 小时前
掌握Python三大语句:顺序、条件与循环
开发语言·python·numpy