質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
JUnit

JUnitは、Javaで開発されたプログラムのユニットテストを行うためのアプリケーションフレームワークです。簡単にプログラムのユニットテストを自動化することができ、結果もわかりやすく表示されるため効率的に開発時間を短縮できます。

Spring Boot

Spring Bootは、Javaのフレームワークの一つ。Springプロジェクトが提供する様々なフレームワークを統合した、アプリケーションを高速で開発するために設計されたフレームワークです。

Q&A

解決済

1回答

4718閲覧

JUnit+Mockitoで深い場所で呼ばれるクラスのmock化

kumaneko

総合スコア15

JUnit

JUnitは、Javaで開発されたプログラムのユニットテストを行うためのアプリケーションフレームワークです。簡単にプログラムのユニットテストを自動化することができ、結果もわかりやすく表示されるため効率的に開発時間を短縮できます。

Spring Boot

Spring Bootは、Javaのフレームワークの一つ。Springプロジェクトが提供する様々なフレームワークを統合した、アプリケーションを高速で開発するために設計されたフレームワークです。

0グッド

0クリップ

投稿2020/07/28 14:24

Spring Bootで開発したソースコードをJUnit+Mockitoでテストを行いたいと考えています。
以下のテストコードでは、ControllerとServiceの処理を確認するために、RepositoryのみをMock化したいと考えています。
( 本当は、Controller、Serviceでそれぞれテストコードを分けるべきだとは思っているのですが... )
Serviceの処理をMock化するのであれば、テストコードの変数controllerに@InjectMocksを付与し、
@Mockを付与したServiceの宣言を追加し、when(xxxx).thenReturn(xxxx)でServiceのメソッドで返したい値を定義してやればよいと思うのですが、ServiceではなくRepositoryのみをMock化したい場合はどうすればよいのかわかりません。
(というよりもそんなことができるのかもわかりません。)

何か方法はありますか?
申し訳ありませんが、ご教示をお願いします。

■テストコード

Java

1@RestController 2@RequiredArgsConstructor 3public class ControllerTest { 4 5 MockMvc mockMvc; 6 7 @Autowired 8 private Controller controller; 9 10 @BeforeEach 11 public void initmocks() { 12 MockitoAnnotations.initMocks(this); 13 mockMvc = MockMvcBuilders.standaloneSetup(controller).build(); 14 } 15 16 @Test 17 public void test01() throws Exception { 18 19 ResultActions results = mockMvc.perform(post("/items")); 20 // 以下、略 21 } 22}

■Controller

Java

1@RestController 2@RequiredArgsConstructor 3public class Controller { 4 5 @Autowired 6 Service service; 7 8 @PostMapping("/items") 9 public ResponseEntity<ItemsResponse> getItemsByPost(@RequestBody body){ 10 String s1 = service.serviceMethod(); 11 // 以下、略 12 } 13}

■Service

Java

1@Service 2@RequiredArgsConstructor 3public class Service { 4 5 @Autowired 6 Repository repository; 7 8 @Transactional(readOnly = true) 9 public String serviceMethod() { 10 String s2 = repository.selectOneXXX(); 11 // 以下、略 12 // repository.selectOneXXX()の結果を受け取っていろいろ処理をする。 13 return 処理結果 14 } 15}

■Repository

Java

1@Repository 2public class Repository { 3 public String selectOneXXX() 4}

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

rubytomato

2020/07/31 06:54

2点確認させてください。 ・やりたいことはコントローラの結合テストで、ただDBアクセスを発生させたくないのでリポジトリをmock化したいということですか? ・Spring Boot、JUnitのバージョンはいくつですか?
kumaneko

2020/07/31 11:14

ご回答ありがとうございます。 > ・やりたいことはコントローラの結合テストで、ただDBアクセスを発生させたくないのでリポジトリをmock化したいということですか? その通りです。 ・Spring Boot、JUnitのバージョンはいくつですか? Spring Boot 2.0.0.M3 Junit 4.12 となります。
rubytomato

2020/07/31 12:08

バージョンの追記ありがとうございます。バージョンで確認したいのですが、Spring Bootは2.0.0.M3で間違いないでしょうか? と言うのも、2.0.xはすでにサポート対象外であり、M3と付いているのは開発途中のバージョンであるためです。
kumaneko

2020/07/31 15:19

ご回答ありがとうございます。 間違いありません。 というのも、商用システムではなく、外部につながることもないため、Springのバージョンアップもせずとも今のところ不都合がないため、作成した当時のまま来ています。 よろしくお願いいたします。
guest

回答1

0

ベストアンサー

・やりたいことはコントローラの結合テストで、ただDBアクセスを発生させたくないのでリポジトリを> mock化したいということですか?

その通りです。

私の知っている範囲では『コントローラーの結合テストにおいて、リポジトリをモック化してデータベースアクセスは行わないようにしたい』ができるかどうかは分からないのですが、代替案が2つあります。
どちらか参考になれば幸いです。

サンプルコード

テスト対象のサンプルコードは以下の通りです。Memoエンティティのデータを1件または全件出力するコードです。

環境

  • JDK 1.8
  • Spring Boot 2.0.1.RELEASE
  • JUnit 4.12
  • MySQL 5.6.25

リポジトリ

java

1import com.example.demo.model.Memo; 2import org.springframework.data.jpa.repository.JpaRepository; 3 4public interface MemoRepository extends JpaRepository<Memo, Long> { 5}

サービス

java

1import com.example.demo.model.Memo; 2 3import java.util.List; 4import java.util.Optional; 5 6public interface MemoService { 7 Optional<Memo> findById(Long id); 8 List<Memo> findAll(); 9}

java

1import com.example.demo.model.Memo; 2import com.example.demo.repository.MemoRepository; 3import com.example.demo.service.MemoService; 4import org.springframework.stereotype.Service; 5 6import java.util.List; 7import java.util.Optional; 8 9@Service 10public class MemoServiceImpl implements MemoService { 11 private final MemoRepository memoRepository; 12 13 public MemoServiceImpl(MemoRepository memoRepository) { 14 this.memoRepository = memoRepository; 15 } 16 17 @Override 18 public Optional<Memo> findById(Long id) { 19 return memoRepository.findById(id); 20 } 21 22 @Override 23 public List<Memo> findAll() { 24 return memoRepository.findAll(); 25 } 26 27}

コントローラー

java

1import com.example.demo.model.Memo; 2import com.example.demo.service.MemoService; 3import org.springframework.http.MediaType; 4import org.springframework.http.ResponseEntity; 5import org.springframework.web.bind.annotation.GetMapping; 6import org.springframework.web.bind.annotation.PathVariable; 7import org.springframework.web.bind.annotation.RequestMapping; 8import org.springframework.web.bind.annotation.RestController; 9 10import java.util.List; 11import java.util.Optional; 12 13@RestController 14@RequestMapping("memo") 15public class MemoController { 16 private final MemoService memoService; 17 18 public MemoController(MemoService memoService) { 19 this.memoService = memoService; 20 } 21 22 @GetMapping(value = "{id}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE) 23 public ResponseEntity<Memo> findById(@PathVariable("id") Long id) { 24 Optional<Memo> memo = memoService.findById(id); 25 return memo.map(ResponseEntity::ok) 26 .orElseGet(() -> ResponseEntity.notFound().build()); 27 } 28 29 @GetMapping("all") 30 public ResponseEntity<List<Memo>> findAll() { 31 List<Memo> memos = memoService.findAll(); 32 return ResponseEntity.ok(memos); 33 } 34 35}

代替案

  1. は結合テストとして実装できますが、モック化はしない代わりに事前準備が必要です。
  2. は単体テストとしての実装になりますが、モック化するので1)に比べて事前準備は少ないです。

結合テストとしての実装が要求されているのであれば1)、そこまで要求されていないのであれば1) or 2)が選択できると思います。

1) リポジトリをモック化せず、H2などのインメモリデータベースを使用する

特徴

  • 結合テストとして実装する。
  • データベースにインメモリデータベースを利用する。
  • モック化は行わない。
  • リポジトリでデータベース固有の関数を使ったネイティブSQLを使っている場合は、テストできない可能性がある。
H2を依存関係に追加する

xml

1<dependency> 2 <groupId>com.h2database</groupId> 3 <artifactId>h2</artifactId> 4 <scope>test</scope> 5</dependency>
テスト用のプロパティファイルを用意する

テスト時にH2データベースにアクセス

src/test/resources/application.test.properties

spring.datasource.url = jdbc:h2:mem:testdb;DB_CLOSE_ON_EXIT=FALSE; spring.datasource.username = sa spring.datasource.password =
テスト時に使用するテストデータを用意する

src/test/resources/import.sql

sql

1INSERT INTO memo (id, title, description, done, updated) VALUES (1001, 'H2 Test title 1001', 'H2 Test description 1001', false, '2020-01-01'); 2INSERT INTO memo (id, title, description, done, updated) VALUES (1002, 'H2 Test title 1002', 'H2 Test description 1002', false, '2020-02-02'); 3INSERT INTO memo (id, title, description, done, updated) VALUES (1003, 'H2 Test title 1003', 'H2 Test description 1003', false, '2020-03-03'); 4INSERT INTO memo (id, title, description, done, updated) VALUES (1004, 'H2 Test title 1004', 'H2 Test description 1004', false, '2020-04-04'); 5INSERT INTO memo (id, title, description, done, updated) VALUES (1005, 'H2 Test title 1005', 'H2 Test description 1005', false, '2020-05-05');
コントローラーのテストコード

テスト時にデータベースアクセスが行われますが、H2を利用するため実行環境を選びません。

java

1import com.example.demo.model.Memo; 2import org.junit.Test; 3import org.junit.runner.RunWith; 4import org.springframework.beans.factory.annotation.Autowired; 5import org.springframework.boot.test.context.SpringBootTest; 6import org.springframework.boot.test.web.client.TestRestTemplate; 7import org.springframework.http.HttpStatus; 8import org.springframework.http.MediaType; 9import org.springframework.http.ResponseEntity; 10import org.springframework.test.context.TestPropertySource; 11import org.springframework.test.context.junit4.SpringRunner; 12 13import static org.assertj.core.api.Assertions.assertThat; 14 15@RunWith(SpringRunner.class) 16@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 17@TestPropertySource(locations = "/application.test.properties") 18public class MemoControllerIntegrationTests { 19 20 @Autowired 21 private TestRestTemplate testRestTemplate; 22 23 @Test 24 public void getMemo() { 25 ResponseEntity<Memo> result = testRestTemplate.getForEntity("/memo/1001", Memo.class); 26 27 assertThat(result).isNotNull(); 28 assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); 29 assertThat(result.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_JSON_UTF8); 30 assertThat(result.getBody().getId()).isEqualTo(1001L); 31 } 32 33 @Test 34 public void getMemos() { 35 ResponseEntity<Memo[]> result = testRestTemplate.getForEntity("/memo/all", Memo[].class); 36 37 assertThat(result).isNotNull(); 38 assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); 39 assertThat(result.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_JSON_UTF8); 40 assertThat(result.getBody()).hasSize(5); 41 } 42 43}

2) リポジトリをモック化するが、単体テストとして実装する

特徴

  • 単体テストとして実装する。
  • リポジトリをモック化する。
  • データベースアクセスは行わない。
コントローラーのテストコード

テスト時にリポジトリをモック化するのでデータベースアクセスは行われません。

java

1import com.example.demo.model.Memo; 2import com.example.demo.repository.MemoRepository; 3import com.example.demo.service.impl.MemoServiceImpl; 4import com.fasterxml.jackson.databind.ObjectMapper; 5import org.junit.Test; 6import org.junit.runner.RunWith; 7import org.mockito.Mockito; 8import org.springframework.beans.factory.annotation.Autowired; 9import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 10import org.springframework.boot.test.mock.mockito.MockBean; 11import org.springframework.boot.test.mock.mockito.SpyBean; 12import org.springframework.http.MediaType; 13import org.springframework.test.context.junit4.SpringRunner; 14import org.springframework.test.web.servlet.MockMvc; 15import org.springframework.test.web.servlet.MvcResult; 16import org.springframework.test.web.servlet.RequestBuilder; 17import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; 18 19import java.nio.charset.StandardCharsets; 20import java.util.Arrays; 21import java.util.List; 22import java.util.Optional; 23 24import static org.assertj.core.api.Assertions.assertThat; 25import static org.mockito.ArgumentMatchers.anyLong; 26 27import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; 28import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 29 30@RunWith(SpringRunner.class) 31@WebMvcTest(MemoController.class) 32public class MemoControllerTests { 33 34 @Autowired 35 private MockMvc mvc; 36 @Autowired 37 private ObjectMapper objectMapper; 38 39 @MockBean 40 private MemoRepository memoRepository; 41 42 @SpyBean 43 private MemoServiceImpl memoService; 44 45 private MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(), 46 MediaType.APPLICATION_JSON.getSubtype(), StandardCharsets.UTF_8); 47 48 @Test 49 public void getMemo() throws Exception { 50 Memo expected = Memo.of(1L, "test title", "test description"); 51 Mockito.when(memoRepository.findById(anyLong())).thenReturn(Optional.ofNullable(expected)); 52 53 RequestBuilder builder = MockMvcRequestBuilders.get("/memo/{id}", 1L) 54 .accept(MediaType.APPLICATION_JSON_UTF8); 55 56 MvcResult result = mvc.perform(builder) 57 .andExpect(status().isOk()) 58 .andExpect(content().contentType(contentType)) 59 .andReturn(); 60 61 String expectedJson = objectMapper.writeValueAsString(expected); 62 assertThat(result.getResponse().getContentAsString()).isEqualTo(expectedJson); 63 } 64 65 @Test 66 public void getMemos() throws Exception { 67 List<Memo> expected = Arrays.asList( 68 Memo.of(1L, "test title 1", "test description 1"), 69 Memo.of(2L, "test title 2", "test description 2"), 70 Memo.of(3L, "test title 3", "test description 3") 71 ); 72 Mockito.when(memoRepository.findAll()).thenReturn(expected); 73 74 RequestBuilder builder = MockMvcRequestBuilders.get("/memo/all") 75 .accept(MediaType.APPLICATION_JSON_UTF8); 76 77 MvcResult result = mvc.perform(builder) 78 .andExpect(status().isOk()) 79 .andExpect(content().contentType(contentType)) 80 .andReturn(); 81 82 String expectedJson = objectMapper.writeValueAsString(expected); 83 assertThat(result.getResponse().getContentAsString()).isEqualTo(expectedJson); 84 } 85}

投稿2020/08/01 16:15

rubytomato

総合スコア1752

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

kumaneko

2020/08/02 12:27

案1で進められそうです。ご回答ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問