Mockito:Java单元测试Mock框架
文章目录
- 一、写在前面
- 1、简介
- 2、依赖
- 二、使用
- 1、基本使用
- 2、注解
- (1)开启注解
- (2)@Mock 注解
- (3)@DoNotMock 注解
- (4)@Spy 注解
- (5)@Captor 注解
- (6)@InjectMocks 注解
- (7)将Mock注入Spy中
- (8)使用注解时遇到空指针
- 3、Mockito模拟抛出异常
- (1)非Void返回值
- (2)Void返回值
- (3)异常作为对象
- (4)模拟对象(spy)
- 4、When/Then 用法
- 5、Mockito Verify用法
- 6、Mockito模拟返回值为void的方法
- (1)简单的模拟与验证
- (2)参数捕获
- (3)回答对void的调用
- (4)部分模拟
- 7、Mockito模拟final类和方法
- 9、Mockito模拟静态方法
- (1)Mock 无参静态方法
- (2)mock带有参数的静态方法
- (3)解决MockitoException:Deregistering Existing Mock Registrations
- 三、Spring中使用Mockito
- 1、Spring中使用Mockito案例
一、写在前面
1、简介
参考资料:https://www.baeldung-cn.com/mockito-series
在进行单元测试时,如果依赖的服务尚未开发完成,或依赖的对象不方便构造,这时我们就需要模拟( Mock)对象。
2、依赖
如果是普通java项目,需要引入mockito-core
,对于 Spring Boot 用户,spring-boot-starter-test
中已经集成好了Mockito,无需配置。
<dependency><groupId>org.mockito</groupId><artifactId>mockito-core</artifactId><version>3.12.4</version><scope>test</scope>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId>
</dependency>
二、使用
1、基本使用
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)开启注解
开启注解有三种方式:
//方法一:在JUnit 上设置 MockitoJUnitRunner
@ExtendWith(MockitoExtension.class)
public class MockitoAnnotationUnitTest {...
}// 方法二:手动编码,调用 MockitoAnnotations.openMocks() 方法
@Before
public void init() {MockitoAnnotations.openMocks(this);
}//最后, 我们可以使用 MockitoJUnit.rule():
public class MockitoAnnotationsInitWithMockitoJUnitRuleUnitTest {//注意,这需要将rule 设置为 public@Rulepublic MockitoRule initRule = MockitoJUnit.rule();...
}
(2)@Mock 注解
@Mock 是 Mockito 中用的最多的注解,我们用它来创建并注入mock对象,而不用手动调用 Mockito.mock 方法。
@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 注解可以完成以上编码的工作。
@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的类或接口
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。
@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 注解的方式完成相同的工作:
@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:
@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:
@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 中:
@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 类:
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:
@Mock
Map<String, String> wordMap;@Spy
MyDictionary spyDic = new MyDictionary();
然而,Mockito 并不支持将mock注入spy,因此下面的测试会出现异常:
@Test
public void whenUseInjectMocksAnnotation_thenCorrect() { Mockito.when(wordMap.get("aWord")).thenReturn("aMeaning"); assertEquals("aMeaning", spyDic.getMeaning("aWord"));
}
如果我们想在 spy 中使用 mock,可以通过构造函数手动注入 mock:
MyDictionary(Map<String, String> wordMap) {this.wordMap = wordMap;
}
现在需要我们手动创建spy,而不使用注释:
@Mock
Map<String, String> wordMap; MyDictionary spyDic;@BeforeEach
public void init() {MockitoAnnotations.openMocks(this);spyDic = Mockito.spy(new MyDictionary(wordMap));
}
现在测试将通过。
(8)使用注解时遇到空指针
通常,当我们使用 @Mock 或 @Spy 注解时,可能会遇到 NullPointerException 异常:
public class MockitoAnnotationsUninitializedUnitTest {@MockList<String> mockedList;@Test(expected = NullPointerException.class)public void whenMockitoAnnotationsUninitialized_thenNPEThrown() {Mockito.when(mockedList.size()).thenReturn(1);}
}
大多数情况下,是因为我们没有启用 Mockito 注解。所以请查看我们第一节的内容,使用Mockito前别忘了先初始化。
3、Mockito模拟抛出异常
测试类:
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():
@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():
@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)异常作为对象
为了配置异常本身,我们可以像之前示例那样传递异常的类,也可以作为对象:
@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(),我们也可以这样做:
@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)配置抛出异常:
@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 用法
测试类:
public class MyList extends AbstractList<String> {@Overridepublic String get(final int index) {return null;}@Overridepublic int size() {return 1;}
}
为mock配置简单返回行为:
MyList listMock = mock(MyList.class);
when(listMock.add(anyString())).thenReturn(false);boolean added = listMock.add(randomAlphabetic(6));
assertThat(added).isFalse();
以另一种方式为mock配置返回行为:
MyList listMock = mock(MyList.class);
doReturn(false).when(listMock).add(anyString());boolean added = listMock.add(randomAlphabetic(6));
assertThat(added).isFalse();
配置mock在方法调用时抛出异常:
MyList listMock = mock(MyList.class);
when(listMock.add(anyString())).thenThrow(IllegalStateException.class);assertThrows(IllegalStateException.class, () -> listMock.add(randomAlphabetic(6)));
配置具有void返回类型的方法的行为——抛出异常:
MyList listMock = mock(MyList.class);
doThrow(NullPointerException.class).when(listMock).clear();assertThrows(NullPointerException.class, () -> listMock.clear());
配置多次调用的行为:
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的行为:
MyList instance = new MyList();
MyList spy = spy(instance);doThrow(NullPointerException.class).when(spy).size();assertThrows(NullPointerException.class, () -> spy.size());
配置mock调用实际底层方法的行为:
MyList listMock = mock(MyList.class);
when(listMock.size()).thenCallRealMethod();assertThat(listMock).hasSize(1);
配置mock方法调用自定义Answer:
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:
public class MyList extends AbstractList<String> {@Overridepublic String get(final int index) {return null;}@Overridepublic int size() {return 1;}
}
简单验证:
List<String> mockedList = mock(MyList.class);
mockedList.size();// 验证 size() 方法是否被调用
verify(mockedList).size();
验证mock的调用次数:
List<String> mockedList = mock(MyList.class);
mockedList.size();
// 验证 size() 方法是否被调用了 1 次
verify(mockedList, times(1)).size();
验证 mock 对象的所有方法都没有被调用:
List<String> mockedList = mock(MyList.class);
verifyNoInteractions(mockedList);
验证 mock 的某个方法被调用:
List<String> mockedList = mock(MyList.class);
verify(mockedList, times(0)).size();
验证没有额外的调用:
List<String> mockedList = mock(MyList.class);
mockedList.size();
mockedList.clear();verify(mockedList).size();
// 除了 size() 外,clear()也被调用了,所以下面会抛出异常
assertThrows(NoInteractionsWanted.class, () -> verifyNoMoreInteractions(mockedList));
验证调用顺序:
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();
验证没有调用某个方法:
List<String> mockedList = mock(MyList.class);
mockedList.size();verify(mockedList, never()).clear();
验证至少调用次数:
List<String> mockedList = mock(MyList.class);
mockedList.clear();
mockedList.clear();
mockedList.clear();verify(mockedList, atLeast(1)).clear();
verify(mockedList, atMost(10)).clear();
验证调用时实际传入的参数:
List<String> mockedList = mock(MyList.class);
mockedList.add("test");verify(mockedList).add("test");
验证调用时传入的参数,不关心具体值:
List<String> mockedList = mock(MyList.class);
mockedList.add("test");verify(mockedList).add(anyString());
验证调用时传入的参数,并捕获参数:
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的方法
测试类:
public class MyList extends AbstractList<String> {@Overridepublic void add(int index, String element) {// no-op}
}
(1)简单的模拟与验证
void方法可以与Mockito的doNothing()、doThrow()和doAnswer()方法一起使用,使模拟和验证变得直观:
@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()与上面的实现相同:
@Test
void whenAddCalled_thenVerified() {MyList myList = mock(MyList.class);myList.add(0, "");verify(myList, times(1)).add(0, "");
}
doThrow()会抛出一个异常:
@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:
@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来添加我们需要的行为:
@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方法:
@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 方法及静态方法的模拟支持。
测试类:
public class MyList extends AbstractList<String> {final public int finalMethod() {return 0;}
}
public final class FinalList extends MyList {@Overridepublic int size() {return 1;}
}
用法没有什么不同,
@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)的面向对象 代码时,我们不应该需要模拟静态类。这通常暗示了我们的应用存在设计问题。
为什么呢?首先,依赖于静态方法的类具有紧密的耦合性,其次,它几乎总是导致难以测试的代码。理想情况下,一个类不应当负责获取其依赖项,如果可能的话,它们应该由外部注入。
测试类:
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接口。
@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带有参数的静态方法
用法和无参静态方法类似,除了需要指定模拟的参数。
@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” 异常的完整示例:
public class StaticMockRegistrationUnitTest {private MockedStatic<StaticUtils> mockStatic;@Beforepublic void setUp() {// Registering a static mock for UserService before each testmockStatic = mockStatic(StaticUtils.class);}@Afterpublic void tearDown() {// Closing the mockStatic after each testmockStatic.close();}@Testpublic void givenStaticMockRegistration_whenMocked_thenReturnsMockSuccessfully() {// Ensure that the static mock for UserService is registeredassertTrue(Mockito.mockingDetails(StaticUtils.class).isMock());}@Testpublic void givenAnotherStaticMockRegistration_whenMocked_thenReturnsMockSuccessfully() {// Ensure that the static mock for UserService is registeredassertTrue(Mockito.mockingDetails(StaticUtils.class).isMock());}
}
在上述示例中,带有 @Before 注解的 setUp() 方法会在每个测试用例之前执行,确保一致的测试环境。在这个方法中,使用 mockStatic(StaticUtils.class) 为 StaticUtils 注册静态模拟。这个注册过程确保每个测试前都会实例化一个新的静态模拟,保持测试的独立性,防止测试用例之间相互干扰。
相反,@After 注解的 tearDown() 方法会在每个测试用例后执行,释放测试执行期间获取的所有资源。
这个细致的设置和清理流程确保每个测试用例在其控制的环境中运行,促进可靠和可重现的测试结果,同时遵循单元测试的最佳实践。
三、Spring中使用Mockito
1、Spring中使用Mockito案例
首先,我们必须配置测试的应用上下文:
@Profile("test")
@Configuration
public class NameServiceTestConfiguration {@Bean@Primarypublic NameService nameService() {return Mockito.mock(NameService.class);}
}
@Profile注解告诉Spring只有在“test”配置活跃时才应用此配置。@Primary注解确保在自动装配时使用这个实例而不是真实实例。方法本身创建并返回我们的NameService类的Mockito模拟。
接下来我们可以编写单元测试:
@ActiveProfiles("test")
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = MocksApplication.class)
public class UserServiceUnitTest {@Autowiredprivate UserService userService;@Autowiredprivate NameService nameService;@Testpublic 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)注解。