前提・実現したいこと
[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