一般使用Spring,都會用到依賴注入(DI)。
@Service
public class SampleService {
@Autowired
private SampleDependency dependency;
public String foo() {
return dependency.getExternalValue("bar");
}
}
如果測試中需要對Sping注入的對象進(jìn)行注入,該怎么做呢?
選擇一 修改實現(xiàn)
一種做法是把字段注入改為構(gòu)造函數(shù)注入:
@Service
public class SampleService {
private SampleDependency dependency;
@Autowired
public SampleService(SampleDependency dependency, PersonPoolProvider personPoolProvider) {
this.dependency = dependency;
}
}
或者屬性注入:
private SampleDependency dependency;
@Autowired
public void setDependency(SampleDependency dependency) {
this.dependency = dependency;
}
測試就可以寫成
SampleDependency dependency = mock(SampleDependency.class);
SampleService service = new SampleService(dependency);
從道理來講這樣更加規(guī)范一些。不過事實上會產(chǎn)生更多的代碼,在字段增刪的時候、構(gòu)造函數(shù)、getter也需要隨之維護(hù)。
選擇二 繞過限制
也可以用一些繞過訪問級別的“黑魔法”,比如測試寫成這樣
SampleDependency dependency = mock(SampleDependency.class);
SampleService service = new SampleService();
ReflectionTestUtils.setField(service, "dependency", dependency);
總感覺不太優(yōu)雅,而且萬一字段改名也很可能漏改。
當(dāng)然,也可以直接把字段改為package可見甚至public。不過總覺得對不起自己的代碼潔癖。
選擇三 使用Mockito InjectMocks
這里推薦使用mockito 的InjectMocks注解。測試可以寫成
@Rule public MockitoRule rule = MockitoJUnit.rule();
@Mock SampleDependency dependency;
@InjectMocks SampleService sampleService;
對應(yīng)于實現(xiàn)代碼中的每個@Autowired字段,測試中可以用一個@Mock聲明mock對象,并用@InjectMocks標(biāo)示需要注入的對象。
這里的
MockitoRule的作用是初始化mock對象和進(jìn)行注入的。有三種方式做這件事。
- 測試
@RunWith(MockitoJUnitRunner.class)- 使用rule
@Rule public MockitoRule rule = MockitoJUnit.rule();- 調(diào)用
MockitoAnnotations.initMocks(this),一般在setup方法中調(diào)用
InjectMocks可以和Sping的依賴注入結(jié)合使用。比如:
@RunWith(SpringRunner.class)
@DirtiesContext
@SpringBootTest
public class ServiceWithMockTest {
@Rule public MockitoRule rule = MockitoJUnit.rule();
@Mock DependencyA dependencyA;
@Autowired @InjectMocks SampleService sampleService;
@Test
public void testDependency() {
when(dependencyA.getExternalValue(anyString())).thenReturn("mock val: A");
assertEquals("mock val: A", sampleService.foo());
}
}
假定Service注入了2個依賴dependencyA, dependencyB。上面測試使用Spring注入了B,把A替換為mock對象。
需要注意的是,Spring test默認(rèn)會重用bean。如果另有一個測試也使用注入的SampleService并在這個測試之后運行,那么拿到service中的dependencyA仍然是mock對象。一般這是不期望的。所以需要用@DirtiesContext修飾上面的測試避免這個問題。
選擇四 Springboot MockBean
如果使用的是Springboot,測試可以用MockBean更簡單的寫出等價的測試。
@RunWith(SpringRunner.class)
@SpringBootTest
public class ServiceWithMockBeanTest {
@MockBean SampleDependencyA dependencyA;
@Autowired SampleService sampleService;
@Test
public void testDependency() {
when(dependencyA.getExternalValue(anyString())).thenReturn("mock val: A");
assertEquals("mock val: A", sampleService.foo());
}
}