一般使用Spring嘉赎,都會用到依賴注入(DI)。
@Service
public class SampleService {
@Autowired
private SampleDependency dependency;
public String foo() {
return dependency.getExternalValue("bar");
}
}
如果測試中需要對Sping注入的對象進行注入蔗崎,該怎么做呢择吊?
選擇一 修改實現(xiàn)
一種做法是把字段注入改為構造函數(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)生更多的代碼攒庵,在字段增刪的時候嘴纺、構造函數(shù)、getter也需要隨之維護叙甸。
選擇二 繞過限制
也可以用一些繞過訪問級別的“黑魔法”颖医,比如測試寫成這樣
SampleDependency dependency = mock(SampleDependency.class);
SampleService service = new SampleService();
ReflectionTestUtils.setField(service, "dependency", dependency);
總感覺不太優(yōu)雅位衩,而且萬一字段改名也很可能漏改裆蒸。
當然,也可以直接把字段改為package可見甚至public糖驴。不過總覺得對不起自己的代碼潔癖僚祷。
選擇三 使用Mockito InjectMocks
這里推薦使用mockito 的InjectMocks注解。測試可以寫成
@Rule public MockitoRule rule = MockitoJUnit.rule();
@Mock SampleDependency dependency;
@InjectMocks SampleService sampleService;
對應于實現(xiàn)代碼中的每個@Autowired
字段贮缕,測試中可以用一個@Mock
聲明mock對象辙谜,并用@InjectMocks
標示需要注入的對象。
這里的
MockitoRule
的作用是初始化mock對象和進行注入的感昼。有三種方式做這件事装哆。
- 測試
@RunWith(MockitoJUnitRunner.class)
- 使用rule
@Rule public MockitoRule rule = MockitoJUnit.rule();
- 調用
MockitoAnnotations.initMocks(this)
,一般在setup方法中調用
InjectMocks可以和Sping的依賴注入結合使用定嗓。比如:
@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默認會重用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());
}
}