やりたいこと
呼び出しが、以下のような感じ
Controller -> Service -> Repository -> Component
Controller
からとかService
からテスト書く時に@Mock
と@InjectMocks
ではComponent
のBeanをモック化できなかったので@MockBean
を使用することに
環境
- 諸事情あり、JUnit4を使ってます
build.gradle
plugins {
id 'org.springframework.boot' version '2.2.5.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'java'
}
group = 'com.example.mock'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation('org.springframework.boot:spring-boot-starter-test')
}
test {
useJUnitPlatform()
}
🍥MockBean
ソース
SampleContoller.java
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/")
public class SampleController {
SampleService sampleService;
public SampleController(SampleService sampleService) {
this.sampleService = sampleService;
}
public String controllerから文字列返す() {
return sampleService.serviceから文字列返す();
}
}
SampleService.java
import org.springframework.stereotype.Service;
@Service
public class SampleService {
SampleRepository sampleRepository;
public SampleService(SampleRepository sampleRepository) {
this.sampleRepository = sampleRepository;
}
String serviceから文字列返す() {
return sampleRepository.repositoryから文字列返す();
}
}
SampleRepository.java
import org.springframework.stereotype.Repository;
@Repository
public class SampleRepository {
SampleApi sampleApi;
public SampleRepository(SampleApi sampleApi) {
this.sampleApi = sampleApi;
}
String repositoryから文字列返す() {
return sampleApi.sampleApiから文字列返す();
}
}
SampleApi.java
import org.springframework.stereotype.Component;
/**
* 外部サービスへ呼び出しのクラス
*/
@Component
public class SampleApi {
public String sampleApiから文字列返す() {
return "駆け出しエンジニア";
}
}
SampleControllerTest.java
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
@SpringBootTest
@RunWith(SpringRunner.class)
public class SampleControllerTest {
@MockBean
SampleApi sampleApi;
@Autowired
SampleController sut;
@Test
public void mockBean使ってテスト() {
when(sampleApi.sampleApiから文字列返す()).thenReturn("抜け出しエンジニア");
assertThat(sut.controllerから文字列返す()).isEqualTo("駆け出しエンジニア");
}
}
結果
モックされてる
org.opentest4j.AssertionFailedError:
Expecting:
<"抜け出しエンジニア">
to be equal to:
<"駆け出しエンジニア">
but was not.
Expected :駆け出しエンジニア
Actual :抜け出しエンジニア
🍥Mockでも試してみる
使うクラスは上と一緒です
@Mock
つけて、テスト対象クラスに@InjectMock
SampleController -> SampleApi
ソース
SampleControllerTest.java
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
@SpringBootTest
@RunWith(SpringRunner.class)
public class SampleControllerTest {
@Mock
SampleApi sampleApi;
@InjectMocks
@Autowired
SampleController sut;
@Test
public void mockBean使ってテスト() {
initMocks(this);
when(sampleApi.sampleApiから文字列返す()).thenReturn("抜け出しエンジニア");
assertThat(sut.controllerから文字列返す()).isEqualTo("駆け出しエンジニア");
}
}
結果
モックされてる気がしない
.java
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.2.5.RELEASE)
2020-03-09 22:27:32.270 INFO 3106 --- [ main] c.e.mock.mockdemo.SampleControllerTest : Starting SampleControllerTest on kihisakawaryoutsuginoMacBook-Pro.local with PID 3106 (started by ri2kku in /Users/xxx/sandbox/java/mock-demo)
2020-03-09 22:27:32.271 INFO 3106 --- [ main] c.e.mock.mockdemo.SampleControllerTest : No active profile set, falling back to default profiles: default
2020-03-09 22:27:33.164 INFO 3106 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2020-03-09 22:27:33.352 INFO 3106 --- [ main] c.e.mock.mockdemo.SampleControllerTest : Started SampleControllerTest in 1.305 seconds (JVM running for 1.965)
2020-03-09 22:27:33.798 INFO 3106 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
Process finished with exit code 0
SampleController -> SampleService
ソース
SampleControllerTest.java
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
@SpringBootTest
@RunWith(SpringRunner.class)
public class SampleControllerTest {
@Mock
SampleService sampleService;
@InjectMocks
@Autowired
SampleController sut;
@Test
public void mockBean使ってテスト() {
initMocks(this);
when(sampleService.serviceから文字列返す()).thenReturn("抜け出しエンジニア");
assertThat(sut.controllerから文字列返す()).isEqualTo("駆け出しエンジニア");
}
}
結果
モックされてる
org.opentest4j.AssertionFailedError:
Expecting:
<"抜け出しエンジニア">
to be equal to:
<"駆け出しエンジニア">
but was not.
Expected :駆け出しエンジニア
Actual :抜け出しエンジニア
まとめ
モックしたいBeanがテスト対象Beanで宣言されてない場合は、MockBean使うのがいいのかね。
そもそもモックしたくない。