programing

통합 테스트 Spring MVC 컨트롤러에 전체 개체 POST

css3 2023. 8. 8. 21:48

통합 테스트 Spring MVC 컨트롤러에 전체 개체 POST

스프링 mvc 웹 앱을 통합 테스트할 때 모의 요청으로 전체 양식 개체를 전달하는 방법이 있습니까?다음과 같은 매개 변수로 각 필드를 개별적으로 전달할 수 있습니다.

mockMvc.perform(post("/somehwere/new").param("items[0].value","value"));

작은 형태에도 좋습니다.하지만 게시된 개체가 커지면 어떻게 합니까?또한 전체 객체를 게시할 수 있으면 테스트 코드가 더 멋져 보입니다.

특히 확인란으로 여러 항목을 선택한 후 게시하는 것을 테스트하고 싶습니다.물론 저는 하나의 아이템을 시험적으로 게시할 수 있지만, 저는 궁금합니다.

스프링-테스트-mvc가 포함된 스프링 3.2.2를 사용하고 있습니다.

양식에 대한 내 모델은 다음과 같습니다.

NewObject {
    List<Item> selection;
}

이런 전화도 해봤어요

mockMvc.perform(post("/somehwere/new").requestAttr("newObject", newObject) 

다음과 같은 컨트롤러로 이동합니다.

@Controller
@RequestMapping(value = "/somewhere/new")
public class SomewhereController {

    @RequestMapping(method = RequestMethod.POST)
    public String post(
            @ModelAttribute("newObject") NewObject newObject) {
        // ...
    }

그러나 개체가 비어 있습니다(예, 이전에 테스트에서 채웠습니다).

제가 찾은 유일한 해결책은 다음과 같은 @SessionAttribute를 사용하는 것이었습니다.Spring MVC 애플리케이션의 통합 테스트: 양식

하지만 모든 컨트롤러 끝에서 이 기능이 필요한 곳에 complete를 호출하는 것을 기억해야 한다는 생각은 싫습니다.모든 양식 데이터가 세션 내에 있을 필요가 없기 때문에 한 번의 요청에 대해서만 필요합니다.

그래서 지금 제가 생각할 수 있는 유일한 방법은 MockHttpServletRequestBuilder를 사용하여 반사를 사용하여 모든 객체 필드를 .param으로 추가하거나 각 테스트 사례에 대해 개별적으로 추가하는 Util 클래스를 작성하는 것입니다.

모르겠어요, 직감이..

내가 좋아하는 것을 쉽게 만들 수 있는 방법에 대한 생각/생각이 있습니까?(컨트롤러에 직접 전화하는 것 외에는)

감사합니다!

저도 같은 질문을 했는데, JSON 마샬러를 사용하여 해결책이 꽤 간단하다는 것이 밝혀졌습니다.
컨트롤러가 변경하여 서명만 변경하도록 하는 경우@ModelAttribute("newObject")로.@RequestBody다음과 같이:

@Controller
@RequestMapping(value = "/somewhere/new")
public class SomewhereController {

    @RequestMapping(method = RequestMethod.POST)
    public String post(@RequestBody NewObject newObject) {
        // ...
    }
}

그런 다음 테스트에서 다음과 같이 간단히 말할 수 있습니다.

NewObject newObjectInstance = new NewObject();
// setting fields for the NewObject  

mockMvc.perform(MockMvcRequestBuilders.post(uri)
  .content(asJsonString(newObjectInstance))
  .contentType(MediaType.APPLICATION_JSON)
  .accept(MediaType.APPLICATION_JSON));

어디서asJsonString메서드는 다음과 같습니다.

public static String asJsonString(final Object obj) {
    try {
        final ObjectMapper mapper = new ObjectMapper();
        final String jsonContent = mapper.writeValueAsString(obj);
        return jsonContent;
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}  

와의 통합 테스트의 주요 목적 중 하나MockMvc모델 개체가 폼 데이터로 올바르게 채워졌는지 확인합니다.

이 작업을 수행하려면 실제 양식에서 전달되는 양식 데이터를 전달해야 합니다(사용)..param()) 에서 자동 변환을 사용하는 경우NewObject데이터에서, 당신의 테스트는 가능한 문제의 특정 클래스를 다루지 않을 것입니다.NewObject실제 형태와 호환되지 않음).

Spring Boot 1.4를 사용하여 가장 간단한 답을 얻었다고 생각합니다. 테스트 클래스에 대한 Import가 포함되어 있습니다.:

public class SomeClass {  /// this goes in it's own file
//// fields go here
}

import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.http.MediaType
import org.springframework.test.context.junit4.SpringRunner
import org.springframework.test.web.servlet.MockMvc

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status

@RunWith(SpringRunner.class)
@WebMvcTest(SomeController.class)
public class ControllerTest {

  @Autowired private MockMvc mvc;
  @Autowired private ObjectMapper mapper;

  private SomeClass someClass;  //this could be Autowired
                                //, initialized in the test method
                                //, or created in setup block
  @Before
  public void setup() {
    someClass = new SomeClass(); 
  }

  @Test
  public void postTest() {
    String json = mapper.writeValueAsString(someClass);
    mvc.perform(post("/someControllerUrl")
       .contentType(MediaType.APPLICATION_JSON)
       .content(json)
       .accept(MediaType.APPLICATION_JSON))
       .andExpect(status().isOk());
  }

}

저는 이 해결책들의 대부분이 너무 복잡하다고 생각합니다.테스트 컨트롤러에 다음과 같은 기능이 있습니다.

 @Autowired
 private ObjectMapper objectMapper;

휴게소일 경우

@Test
public void test() throws Exception {
   mockMvc.perform(post("/person"))
          .contentType(MediaType.APPLICATION_JSON)
          .content(objectMapper.writeValueAsString(new Person()))
          ...etc
}

게시된 양식을 사용한 봄 mvc를 위해 저는 이 해결책을 생각해냈습니다.(아직 좋은 생각인지 잘 모르겠습니다.)

private MultiValueMap<String, String> toFormParams(Object o, Set<String> excludeFields) throws Exception {
    ObjectReader reader = objectMapper.readerFor(Map.class);
    Map<String, String> map = reader.readValue(objectMapper.writeValueAsString(o));

    MultiValueMap<String, String> multiValueMap = new LinkedMultiValueMap<>();
    map.entrySet().stream()
            .filter(e -> !excludeFields.contains(e.getKey()))
            .forEach(e -> multiValueMap.add(e.getKey(), (e.getValue() == null ? "" : e.getValue())));
    return multiValueMap;
}



@Test
public void test() throws Exception {
  MultiValueMap<String, String> formParams = toFormParams(new Phone(), 
  Set.of("id", "created"));

   mockMvc.perform(post("/person"))
          .contentType(MediaType.APPLICATION_FORM_URLENCODED)
          .params(formParams))
          ...etc
}

는 - 먼저 을 쉽게 입니다. - 이 한 후 입니다. - 본기이를문여문자로환열먼다이아디어는모하든변필쉽이드다름을얻덤것니합프니입게는로자열다으하환음고을변로으맵저체객sonson▁-▁the▁is▁-이▁j▁it▁to▁basic▁idea▁to.MultiValueMap봄이 기대하는.않을 을 달 수도 ).@JsonIgnore단계를 ).

리플렉션을 사용하여 해결할 수 있지만 마샬링을 사용하지 않는 다른 방법:

다음과 같은 추상적인 도우미 클래스가 있습니다.

public abstract class MvcIntegrationTestUtils {

       public static MockHttpServletRequestBuilder postForm(String url,
                 Object modelAttribute, String... propertyPaths) {

              try {
                     MockHttpServletRequestBuilder form = post(url).characterEncoding(
                           "UTF-8").contentType(MediaType.APPLICATION_FORM_URLENCODED);

                     for (String path : propertyPaths) {
                            form.param(path, BeanUtils.getProperty(modelAttribute, path));
                     }

                     return form;

              } catch (Exception e) {
                     throw new RuntimeException(e);
              }
     }
}

다음과 같이 사용합니다.

// static import (optional)
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

// in your test method, populate your model attribute object (yes, works with nested properties)
BlogSetup bgs = new BlogSetup();
      bgs.getBlog().setBlogTitle("Test Blog");
      bgs.getUser().setEmail("admin.localhost@example.com");
    bgs.getUser().setFirstName("Administrator");
      bgs.getUser().setLastName("Localhost");
      bgs.getUser().setPassword("password");

// finally put it together
mockMvc.perform(
            postForm("/blogs/create", bgs, "blog.blogTitle", "user.email",
                    "user.firstName", "user.lastName", "user.password"))
            .andExpect(status().isOk())

폼을 작성할 때 속성 경로를 언급하는 것이 더 낫다고 판단했습니다. 테스트에서 속성 경로를 변경해야 하기 때문입니다.예를 들어 누락된 입력에 대해 유효성 검사 오류가 발생하는지 확인하고 해당 조건을 시뮬레이션하기 위해 속성 경로를 생략할 수 있습니다.또한 @Before 메서드에서 모델 속성을 구축하는 것이 더 쉽습니다.

BeanUtils는 Common-BeanUtils에서 왔습니다.

    <dependency>
        <groupId>commons-beanutils</groupId>
        <artifactId>commons-beanutils</artifactId>
        <version>1.8.3</version>
        <scope>test</scope>
    </dependency>

저는 얼마 전에 같은 문제에 부딪혔고 잭슨의 도움을 받아 반성을 통해 해결했습니다.

먼저 개체의 모든 필드로 맵을 채웁니다.그런 다음 이러한 맵 항목을 MockHttpServletRequestBuilder에 매개 변수로 추가합니다.

이러한 방식으로 모든 개체를 사용할 수 있으며 요청 매개 변수로 전달합니다.다른 솔루션도 있지만 이 솔루션이 우리에게 효과가 있었다고 확신합니다.

    @Test
    public void testFormEdit() throws Exception {
        getMockMvc()
                .perform(
                        addFormParameters(post(servletPath + tableRootUrl + "/" + POST_FORM_EDIT_URL).servletPath(servletPath)
                                .param("entityID", entityId), validEntity)).andDo(print()).andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON)).andExpect(content().string(equalTo(entityId)));
    }

    private MockHttpServletRequestBuilder addFormParameters(MockHttpServletRequestBuilder builder, Object object)
            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {

        SimpleDateFormat dateFormat = new SimpleDateFormat(applicationSettings.getApplicationDateFormat());

        Map<String, ?> propertyValues = getPropertyValues(object, dateFormat);

        for (Entry<String, ?> entry : propertyValues.entrySet()) {
            builder.param(entry.getKey(),
                    Util.prepareDisplayValue(entry.getValue(), applicationSettings.getApplicationDateFormat()));
        }

        return builder;
    }

    private Map<String, ?> getPropertyValues(Object object, DateFormat dateFormat) {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setDateFormat(dateFormat);
        mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        mapper.registerModule(new JodaModule());

        TypeReference<HashMap<String, ?>> typeRef = new TypeReference<HashMap<String, ?>>() {};

        Map<String, ?> returnValues = mapper.convertValue(object, typeRef);

        return returnValues;

    }

할 수 있도록 사용할 수 있습니다.MockHttpServletRequestBuilder

public static void objectToPostParams(final String key, final Object value, final Map<String, String> map) throws IllegalAccessException {
    if ((value instanceof Number) || (value instanceof Enum) || (value instanceof String)) {
        map.put(key, value.toString());
    } else if (value instanceof Date) {
        map.put(key, new SimpleDateFormat("yyyy-MM-dd HH:mm").format((Date) value));
    } else if (value instanceof GenericDTO) {
        final Map<String, Object> fieldsMap = ReflectionUtils.getFieldsMap((GenericDTO) value);
        for (final Entry<String, Object> entry : fieldsMap.entrySet()) {
            final StringBuilder sb = new StringBuilder();
            if (!GenericValidator.isEmpty(key)) {
                sb.append(key).append('.');
            }
            sb.append(entry.getKey());
            objectToPostParams(sb.toString(), entry.getValue(), map);
        }
    } else if (value instanceof List) {
        for (int i = 0; i < ((List) value).size(); i++) {
            objectToPostParams(key + '[' + i + ']', ((List) value).get(i), map);
        }
    }
}

GenericDTO확장된 단순 클래스입니다.Serializable

public interface GenericDTO extends Serializable {}

그리고 여기 있습니다.ReflectionUtils 시간

public final class ReflectionUtils {
    public static List<Field> getAllFields(final List<Field> fields, final Class<?> type) {
        if (type.getSuperclass() != null) {
            getAllFields(fields, type.getSuperclass());
        }
        // if a field is overwritten in the child class, the one in the parent is removed
        fields.addAll(Arrays.asList(type.getDeclaredFields()).stream().map(field -> {
            final Iterator<Field> iterator = fields.iterator();
            while(iterator.hasNext()){
                final Field fieldTmp = iterator.next();
                if (fieldTmp.getName().equals(field.getName())) {
                    iterator.remove();
                    break;
                }
            }
            return field;
        }).collect(Collectors.toList()));
        return fields;
    }

    public static Map<String, Object> getFieldsMap(final GenericDTO genericDTO) throws IllegalAccessException {
        final Map<String, Object> map = new HashMap<>();
        final List<Field> fields = new ArrayList<>();
        getAllFields(fields, genericDTO.getClass());
        for (final Field field : fields) {
            final boolean isFieldAccessible = field.isAccessible();
            field.setAccessible(true);
            map.put(field.getName(), field.get(genericDTO));
            field.setAccessible(isFieldAccessible);
        }
        return map;
    }
}

다음과 같이 사용할 수 있습니다.

final MockHttpServletRequestBuilder post = post("/");
final Map<String, String> map = new TreeMap<>();
objectToPostParams("", genericDTO, map);
for (final Entry<String, String> entry : map.entrySet()) {
    post.param(entry.getKey(), entry.getValue());
}

광범위하게 테스트하지는 않았지만, 효과가 있는 것 같습니다.

다음 구성은 문제를 해결하고 전체 객체를 전송하는 데 도움이 되었습니다.

post(BASE_URL)
            .flashAttr("attr_wrapper", wrapper) // 'flashAttr' helped to add whole object to request

컨트롤러의 내 메소드:

public String updateConfiguration(
      @ModelAttribute("attr_wrapper") Wrapper wrapper) { // don't forget to add name like 'attr_wrapper' 
}

언급URL : https://stackoverflow.com/questions/17143116/integration-testing-posting-an-entire-object-to-spring-mvc-controller