Spring依賴注入與mock

一般使用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());
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容