文章目錄
- 一、寫在前面
- 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)注解。