Spring、Spring Boot和TestNG測試指南 - 使用Mockito

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來說解。

被測試類

先介紹一下接下來要被咱們測試的類FooBar倆兄弟。

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;
  }

}

例子1: 不使用Mock技術

源代碼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:

  1. 匿名內部類

  2. 作了一個FakeBar

這兩種方式都不是很優雅,看下面使用Mockito的例子。

例子2:使用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);

  }

}
  1. 咱們先給了一個Bar的Mock實現:@Mock private Bar bar;

  2. 而後又規定了getAllCodes方法的返回值:when(bar.getAllCodes()).thenReturn(Collections.singleton("123"))。這樣就把一個假的Bar定義好了。

  3. 最後利用Mockito把Bar注入到Foo裏面,@InjectMocks private FooImpl foo;MockitoAnnotations.initMocks(this);

例子3:配合Spring Test

源代碼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)

例子4:配合Spring Test(多層依賴)

當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。

例子5:配合Spring Boot Test

源代碼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);

  }

}

參考文檔

相關文章
相關標籤/搜索