Github地址html
Mock測試技術可以避免你爲了測試一個方法,卻須要自行構建整個依賴關係的工做,而且可以讓你專一於當前被測試對象的邏輯,而不是其依賴的其餘對象的邏輯。java
舉例來講,好比你須要測試Foo.methodA
,而這個方法依賴了Bar.methodB
,又傳遞依賴到了Zoo.methodC
,因而它們的依賴關係就是Foo->Bar->Zoo
,因此在測試代碼裏你必須自行new Bar和Zoo。git
有人會說:"我直接用Spring的DI機制不就好了嗎?"的確,你能夠用Spring的DI機制,不過解決不了測試代碼耦合度太高的問題:github
由於Foo方法內部調用了Bar和Zoo的方法,因此你對其作單元測試的時候,必須徹底瞭解Bar和Zoo方法的內部邏輯,而且謹慎的傳參和assert結果,一旦Bar和Zoo的代碼修改了,你的Foo測試代碼極可能就會運行失敗。spring
因此這個時候咱們須要一種機制,能過讓咱們在測試Foo的時候不依賴於Bar和Zoo的具體實現,即不關心其內部邏輯,只關注Foo內部的邏輯,從而將Foo的每一個邏輯分支都測試到。數據庫
因此業界就產生了Mock技術,它可讓咱們作一個假的Bar(不須要Zoo,由於只有真的Bar才須要Zoo),而後控制這個假的Bar的行爲(讓它返回什麼就返回什麼),以此來測試Foo的每一個邏輯分支。ide
你確定會問,這樣的測試有意義嗎?在真實環境裏Foo用的是真的Bar而不是假的Bar,你用假的Bar測試成功能表明真實環境不出問題?spring-boot
其實假Bar表明的是一個行爲正確的Bar,用它來測試就能驗證"在Bar行爲正確的狀況下Foo的行爲是否正確",而真Bar的行爲是否正確會由它本身的測試代碼來驗證。單元測試
Mock技術的另外一個好處是可以讓你儘可能避免集成測試,好比咱們能夠Mock一個Repository(數據庫操做類),讓咱們儘可能多寫單元測試,提升測試代碼執行效率。測試
spring-boot-starter-test
依賴了Mockito,因此咱們會在本章裏使用Mockito來說解。
public interface Foo { boolean checkCodeDuplicate(String code); } public interface Bar { Set<String> getAllCodes(); } @Component public class FooImpl implements Foo { private Bar bar; @Override public boolean checkCodeDuplicate(String code) { return bar.getAllCodes().contains(code); } @Autowired public void setBar(Bar bar) { this.bar = bar; } }
源代碼NoMockTest:
public class NoMockTest { @Test public void testCheckCodeDuplicate1() throws Exception { FooImpl foo = new FooImpl(); foo.setBar(new Bar() { @Override public Set<String> getAllCodes() { return Collections.singleton("123"); } }); assertEquals(foo.checkCodeDuplicate("123"), true); } @Test public void testCheckCodeDuplicate2() throws Exception { FooImpl foo = new FooImpl(); foo.setBar(new FakeBar(Collections.singleton("123"))); assertEquals(foo.checkCodeDuplicate("123"), true); } public class FakeBar implements Bar { private final Set<String> codes; public FakeBar(Set<String> codes) { this.codes = codes; } @Override public Set<String> getAllCodes() { return codes; } } }
這個測試代碼裏用到了兩種方法來作假的Bar:
匿名內部類
作了一個FakeBar
這兩種方式都不是很優雅,看下面使用Mockito的例子。
源代碼MockitoTest:
public class MockitoTest { @Mock private Bar bar; @InjectMocks private FooImpl foo; @BeforeMethod(alwaysRun = true) public void initMock() { MockitoAnnotations.initMocks(this); } @Test public void testCheckCodeDuplicate() throws Exception { when(bar.getAllCodes()).thenReturn(Collections.singleton("123")); assertEquals(foo.checkCodeDuplicate("123"), true); } }
咱們先給了一個Bar的Mock實現:@Mock private Bar bar;
而後又規定了getAllCodes
方法的返回值:when(bar.getAllCodes()).thenReturn(Collections.singleton("123"))
。這樣就把一個假的Bar定義好了。
最後利用Mockito把Bar注入到Foo裏面,@InjectMocks private FooImpl foo;
、MockitoAnnotations.initMocks(this);
源代碼Spring_1_Test:
@ContextConfiguration(classes = FooImpl.class) @TestExecutionListeners(listeners = MockitoTestExecutionListener.class) public class Spring_1_Test extends AbstractTestNGSpringContextTests { @MockBean private Bar bar; @Autowired private Foo foo; @Test public void testCheckCodeDuplicate() throws Exception { when(bar.getAllCodes()).thenReturn(Collections.singleton("123")); assertEquals(foo.checkCodeDuplicate("123"), true); } }
要注意,若是要啓用Spring和Mockito,必須添加這麼一行:@TestExecutionListeners(listeners = MockitoTestExecutionListener.class)
。
當Bean存在這種依賴關係當時候:LooImpl -> FooImpl -> Bar
,咱們應該怎麼測試呢?
按照Mock測試的原則,這個時候咱們應該mock一個Foo
對象,把這個注入到LooImpl
對象裏,就像例子3裏的同樣。
不過若是你不想mock Foo
而是想mock Bar
的時候,其實作法和前面也差很少,Spring會自動將mock Bar注入到FooImpl
中,而後將FooImpl
注入到LooImpl
中。
源代碼Spring_2_Test:
@ContextConfiguration(classes = { FooImpl.class, LooImpl.class }) @TestExecutionListeners(listeners = MockitoTestExecutionListener.class) public class Spring_2_Test extends AbstractTestNGSpringContextTests { @MockBean private Bar bar; @Autowired private Loo loo; @Test public void testCheckCodeDuplicate() throws Exception { when(bar.getAllCodes()).thenReturn(Collections.singleton("123")); assertEquals(loo.checkCodeDuplicate("123"), true); } }
也就是說,得益於Spring Test Framework,咱們可以很方便地對依賴關係中任意層級的任意Bean作mock。
源代碼Boot_1_Test:
@SpringBoot_1_Test(classes = { FooImpl.class }) @TestExecutionListeners(listeners = MockitoTestExecutionListener.class) public class Boot_1_Test extends AbstractTestNGSpringContextTests { @MockBean private Bar bar; @Autowired private Foo foo; @Test public void testCheckCodeDuplicate() throws Exception { when(bar.getAllCodes()).thenReturn(Collections.singleton("123")); assertEquals(foo.checkCodeDuplicate("123"), true); } }