Front endopint
getMyRcps : function(){
const myRcpsTmpl = _.template($("#myRcpsTmpl").html());
const $myRcpsUl = $(".mypage_myrecipe_tab.tab_wrap ul");
$.ajax({
url: '/mypage/ajax/myrecipes',
type: 'GET',
dataType:'json',
error: function(){ alert('something went wrong')},
success: function(json){
console.log(json);
$myRcpsUl.html(myRcpsTmpl({myRcps:json}));
}
});
}
Api controller
@Slf4j
@RequiredArgsConstructor
@RestController
public class MypageApiController {
private final RecipesService recipesService;
@GetMapping("/mypage/ajax/myrecipes")
public List<Rcp> getMyRecipes(@LoginUser SessionUser user) {
if(user!=null) {
int memberNo = user.getNo();
return recipesService.getRecipesByMemberNo(memberNo);
}
return null;
}
SessionUser
@Getter
public class SessionUser {
private int no, addressNo;
private String email, name, nickname, profileImg;
private char marketkeeperStep;
@Builder
public SessionUser(int no, int addressNo, String email, String name, String nickname, String profileImg, char marketkeeperStep) {
super();
this.no = no;
this.addressNo = addressNo;
this.email = email;
this.name = name;
this.nickname = nickname;
this.profileImg = profileImg;
this.marketkeeperStep = marketkeeperStep;
}
}
@Configuration
@Import(MvcTestConfig.class)
@EnableTransactionManagement
@ComponentScan(basePackages = {"com.ktx.ddep.controller","com.ktx.ddep.service", "com.ktx.ddep.argumentresolver"})
public class ApiTestConfig {
@Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
@Bean
public HttpSession sessionCreator() {
return new MockHttpSession();
}
}
MvcTestConfig
@Configuration
public class MvcTestConfig implements WebMvcConfigurer{
private LoginUserArgResolver loginUserArgResolver;
// enable default servlet when requests other than that made for spring is made
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
// jsp view resolver
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp("/WEB-INF/view/", ".jsp");
}
// redirect request "/" to "/main"
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addRedirectViewController("/", "/main");
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(loginUserArgResolver);
}
}
MypageApiControllerTest.java
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {ApiTestConfig.class})
@Transactional
public class MypageApiControllerTest {
// mock 객체를 생성하고 해당 mock 객체에 Mock으로 설정한 객체들을 주입
@InjectMocks
private MypageApiController apiController;
@Mock
private RecipesService rcpService;
@Mock
private List<Rcp> rcpList;
private MockMvc mockMvc;
@Before
public void instantiate() {
MockitoAnnotations.openMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(apiController).build();
}
@Test
public void givenUser_whenGetRequest_thenIsStatusOk() throws Exception {
//given
SessionUser user = SessionUser.builder()
.no(8)
.addressNo(8)
.email("wlgypark@gmail.com")
.name("박지효")
.nickname("짜릿한슬라임")
.marketkeeperStep('i')
.profileImg("profile.jpg")
.build();
//when
when(apiController.getMyRecipes(user)).thenReturn(rcpList);
//then
mockMvc.perform(get("/mypage/ajax/myrecipes")).andExpect(status().isOk()).andDo(print());
}
}
HttpMessageNotWritableException: nested Exception - jackson.databind.JsonMappingException: cannot invoke 'java.util.Iterator.hasNext()" because it is null
위와 같은 예외가 발생하며 테스트가 실패했다.
따라서 위와 같이 임의의 값을 담은 SessionUser 객체를 생성한 뒤 get 요청을 다시 수행하는 코드를 짠 뒤 테스트를 실행했다. 그러자
[org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult]
Field error in object 'sessionUser' on field 'no': rejected value [null];
[typeMismatch.sessionUser.no,typeMismatch.no,typeMismatch.int,typeMismatch];
arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [sessionUser.no,no]; arguments []; default message [no]];
default message [Failed to convert value of type 'null' to required type 'int'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [null] to type [int] for value 'null'; nested exception is java.lang.IllegalArgumentException: A null value cannot be assigned to a primitive type]
컨트롤러 메서드는 요청의 Accept와 Content-type이 포함된 header를 읽어들여 요청 정보의 미디어 타입을 확인하고, 이어 HttpMessageConverters를 통해 해당 미디어 타입을 핸들링할 수 있는 컨버터를 찾게 된다. 그리고 이 컨버터는 자바 엔티티를 클라이언트가 요청한 포맷으로 가공해주는 역할을 한다. 나는 @RestController
를 사용할 계획이었으므로 json/xml로 응답하여야 했고, 컨버터로 jackson-databind 라이브러리를 사용했다.
During deserialization, the fields of non-serializable classes will be initialized using the public or protected no-arg constructor of the class. A no-arg constructor must be accessible to the subclass that is serializable. The fields of serializable subclasses will be restored from the stream.
references: