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

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

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

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

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Spring Boot

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

Q&A

1回答

2688閲覧

SpringBoot Controllerのテストの書き方について

k.shitsumon

総合スコア0

JUnit

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

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Spring Boot

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

0グッド

0クリップ

投稿2021/06/22 09:56

編集2021/06/23 01:33

前提・実現したいこと

[SpringMVCのテスト]

初心者です。テストコードの作成自体これが初めてです。
SpringMVCで作成したアプリケーションのテストを行いたいのですが、正常に動作しているはずがテストを実行するとエラーが出てしまうため、これを解決したいです。

発生している問題・エラーメッセージ

エラーの内容としては、modelの変数を確認した際に想定した内容ではなくnullになっている、という旨なのは理解出来ましたが、エラーを解決出来ません。

以下[障害トレース]

java.lang.AssertionError: Model attribute 'delete_check' expected:<このアカウントの利用は停止されています。別のアカウントを利用して下さい。> but was:<null> at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:59) at org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:122) at org.springframework.test.web.servlet.result.ModelResultMatchers.lambda$attribute$1(ModelResultMatchers.java:74) at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:196) at com.example.demo.controller.LoginControllerTest.利用停止アカウント(LoginControllerTest.java:143) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:564) at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:688) at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60) at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131) at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149) at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140) at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84) at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115) at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105) at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106) at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64) at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45) at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37) at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104) at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:210) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:206) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:131) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:65) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) (以下略)

該当のソースコード

java

1テストクラス 2package com.example.demo.controller; 3 4import(略) 5 6 7 8@ExtendWith(MockitoExtension.class) 9@SpringBootTest 10public class LoginControllerTest { 11 12 @InjectMocks 13 private LoginController target; 14 15 //テスト対象クラスで呼ばれるクラスのモックオブジェクト 16 @Mock 17 private LoginModel loginmodel; 18 @Mock 19 private LoginRepository loginrepository; 20 @Mock 21 private LoginEntity loginentity; 22 23 private MockMvc mvc; 24 25 26 @BeforeEach 27 public void setup() { 28 MockitoAnnotations.initMocks(this); 29 this.mvc = MockMvcBuilders.standaloneSetup(target).build(); 30 } 31 32 33 34 @Test 35 public void 利用停止アカウント() throws Exception { 36 //LoginEntityのgetDelete_flg()が「1」だった場合の条件分岐をしているので用意する必要がある 37 lenient().when(loginentity.getDelete_flg()).thenReturn("1"); 38 //疑似リクエストを送信 39 mvc.perform(MockMvcRequestBuilders.post("/") 40 //削除フラグが立っているアカウントでログイン 41 .param("emp_id", "00003") 42 .param("password", "pass")) 43 //modelに正しい変数を詰められているか 44 .andExpect(model().attribute("display", false)) 45 .andExpect(model().attribute("delete_check", ErrorMessage.getDelete_check())) 46 //指定のViewを返しているか 47 .andExpect(view().name("loginview")) 48 //レスポンスのHTTPステータスコードは正しいか 49 .andExpect(status().isOk()) 50 .andReturn(); 51 52 } 53 54}
コントローラクラス package com.example.demo.controller; import (略) @Controller public class LoginController { @Autowired(略) //他のページからPOSTでログイン画面に遷移するときの処理 エラーメッセージの表示にも使う @PostMapping("/")//URLはhttp://localhost:8080 public ModelAndView send(@RequestParam Map<String,String> requestParams,//受け取るパラメーター ModelAndView mav) { String emp_id=requestParams.get("emp_id");//社員IDを受け取る String password=requestParams.get("password");//パスワードを受け取る mav.setViewName("loginview");//表示するページの指定 boolean display=false;//エラー時の処理なので今回はfalse if(emp_id=="") {//社員IDが未入力の場合 mav.addObject("emp_id_error","[社員ID]"+ErrorMessage.getBlank_check()); //htmlで表示するエラー文 } if(password=="") { //パスワードが未入力の場合 mav.addObject("password_error", "[パスワード]"+ErrorMessage.getBlank_check());//html表示するエラー文 } if(emp_id!=""&&password!="") { //社員IDとパスワード両方が入力されている場合 //社員IDとパスワードの桁数チェック if(emp_id.length()>5||password.length()>10) { if(emp_id.length()>5) { mav.addObject("emp_id_error", "[社員IDは5]"+ErrorMessage.getDigit_check());//html表示するエラー文 } if(password.length()>10) { mav.addObject("password_error", "[パスワードは10]"+ErrorMessage.getDigit_check());//html表示するエラー文 } }else { LoginEntity delete_flg = LoginService.findLogin(emp_id,password);//社員IDとパスワードが一致するデータがDBにあるか確認するメソッド if(delete_flg==null) { //該当データがない場合 mav.addObject("login_check", ErrorMessage.getLogin_check()); }else{//該当データはあるが削除フラグが立っている場合 if(delete_flg.getDelete_flg().equals("1")) { mav.addObject("delete_check", ErrorMessage.getDelete_check()); }else { display=true; session.setAttribute("emp_id", emp_id); session.setAttribute("password", password); mav.setViewName("menuchange"); } } } } mav.addObject("display", display);//変数の内容で表示分岐 return mav; } }
リポジトリクラス package com.example.demo.repository; import(略) public interface LoginRepository extends JpaRepository<LoginEntity, String> { //下記のSQL文でDBからデータを参照する @Query(value="SELECT M_EMPLOYEE.EMP_ID,M_EMPLOYEE.LOGIN_PASS,M_EMPLOYEE.DELETE_FLG " + "FROM M_EMPLOYEE WHERE M_EMPLOYEE.EMP_ID = :id AND M_EMPLOYEE.LOGIN_PASS = :password",nativeQuery = true) LoginEntity findLogin(String id,String password);//SQL文に渡す変数 }
サービスクラス package com.example.demo.model; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.example.demo.entity.LoginEntity; import com.example.demo.repository.LoginRepository; @Service @Transactional public class LoginModel { @Autowired LoginRepository LoginRepository; public LoginEntity findLogin(String id,String password){ return LoginRepository.findLogin(id,password);//リポジトリクラスからログイン用の処理の呼び出し } }
エンティティクラス package com.example.demo.entity; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; @Entity @Table(name = "M_EMPLOYEE")//DBから参照するテーブル名 //ログイン用エンティティクラス public class LoginEntity { @Id private String emp_id; @Column(nullable = false) private String login_pass; private String delete_flg; public String getEmp_id() { return emp_id; } public void setEmp_id(String emp_id) { this.emp_id = emp_id; } public String getLogin_pass() { return login_pass; } public void setLogin_pass(String login_pass) { this.login_pass = login_pass; } public String getDelete_flg() { return delete_flg; } public void setDelete_flg(String delete_flg) { this.delete_flg = delete_flg; } }

試したこと

文字数の都合上エンティティ、サービスクラスを省略しました。

一部省略していますが、テストクラスで他のエラーパターン(未入力チェック)なども試しています。その際には特に何の問題もなくテスト出来たため、エラーメッセージの取得自体が出来ていない、という可能性は無いと思われます。
問題なく動く他のテストメソッドとの違いを探した結果、エラー表示の条件分岐に「リクエストパラメータの値を使用しているか」と「LoginEntityのgetDelete_flg()メソッドを使用しているか」の違いがあったため、原因はそこだと考えています。

要するにLoginEntityのgetDelete_flg()を「"1"」にすれば動作するのでは?と考えたものの、
when(loginentity.getDelete_flg()).thenReturn("1");
を追加すると不要なスタブ認定されてエラーが出てしまいました。
lenient().を追加することでそのエラーは回避出来たのですが、根本的な解決になっていないという事に気づき、質問させて頂いた次第です。

これ以外にもwhen()内にサービスクラスやリポジトリクラスを書くなどしてみたのですが、私が試した限りでは上手くいきませんでした。

補足情報(FW/ツールのバージョンなど)

Spring Tool Site 4

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

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

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

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

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

m.ts10806

2021/06/22 11:16

変に略さない方が良いかと。 どこに問題が潜んでいるか分かりません。 もし「長いから」という理由でしたら、問題再現できる最小構成のコードを組みなおすと良いです。
k.shitsumon

2021/06/23 01:34

ご指摘ありがとうございます。 出来るだけ略さずに情報を追加しました。
guest

回答1

0

Controller 内で LoginEntity を生成するのに
static なメソッド LoginService.findLogin から直接生成しているからだと思われます。

そのため、delete_flg がmock に差し変わっていないと思います。

LoginEntity delete_flg = LoginService.findLogin(emp_id,password);//社員IDとパスワードが一致するデータがDBにあるか確認するメソッド

findLogin() を非 static なメソッドにしてからLoginService を Controller のメンバーにして、Mockに差し替えた上で mock のLoginEntity を返すようにするか、
static メソッドの LoginService.findLogin を mock 化する必要があります。

投稿2021/06/22 11:46

編集2021/06/22 21:55
kiino

総合スコア557

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

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

k.shitsumon

2021/06/23 01:42

回答ありがとうございます。 私の認識が間違っていたら申し訳ないのですが「staticなメソッド」とは単純にメソッドの型の前にstaticと付けているメソッドのことですよね? 文字数制限を気にして省略してしまっていたのですがLoginService.findLoginは非staticなメソッドです。 サービスクラス、エンティティクラスを追記しましたので、よろしければご確認頂けると幸いです。
kiino

2021/06/23 10:57

コメントありがとうございます。 何点か確認させてください。 Controller の Autowired 以下も省略されていますが、そこについても教えていただけると幸いです。 メンバーをどのように定義しているのか把握したいです。 LoginService というのが記載されていますが、このクラスについても記載お願いできますでしょうか。 LoginModel に @Service アノテーションがついていますが、これが Controller 内で呼び出しているLoginService の実装でしょうか? (大文字からはじまっているのでクラス名だと思っていました。クラスから直接メソッドを呼んでいるように見えたので、static な関数だと思っていました。) 非 static でないということですが、であれば以下の対応は無理そうでしたでしょうか。 > LoginService を Controller のメンバーにして、Mockに差し替えた上で mock のLoginEntity を返すようにする 以上、よろしくお願いいたします。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

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

ただいまの回答率
85.35%

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

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

質問する

関連した質問