반응형

Spring Test MockMvc의 한글 깨짐 처리

스프링에서 테스트 코드를 작성할 때 MockMvc를 흔히 사용한다.

대략 아래와 같이 설정하고 사용한다.

@RunWith(SpringRunner.class)
@SpringBootTest
public class ApiControllerTest {

    private MockMvc mockMvc;

    @Autowired
    private WebApplicationContext ctx;

    @Before
    public void setup() {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(ctx)
                .alwaysDo(print())
                .build();
    }

    @Test
    public void 상품검색() throws Exception {
        String keyword = "sports";

        MvcResult result = this.mockMvc
                .perform(
                        get("/api/search/" + keyword)
                )
                .andExpect(status().isOk())
                .andExpect(어쩌구 Matcher...)
                .andReturn();
    }
}

위의 테스트 코드에서는 한글이 없으므로 아무 문제가 없는데, 아래와 같이 한글을 사용하면 깨진 한글이 Controller에 유입될 수 있으며, 결국 원하는 대로 동작하지 않게 된다.

    @Test
    public void 상품검색() throws Exception {
        String keyword = "스포츠";  // 한글 사용

        MvcResult result = this.mockMvc
                .perform(get("/api/search/" + keyword))
                .andExpect(status().isOk())
                .andExpect(어쩌구 Matcher...)
                .andReturn();
    }

이 문제는 MockMvc를 설정할 때 CharacterEncodingFilter를 추가해주면 쉽게 해결할 수 있다.

    @Before
    public void setup() {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(ctx)
                .addFilters(new CharacterEncodingFilter("UTF-8", true))  // 필터 추가
                .alwaysDo(print())
                .build();
    }



출처 : https://github.com/HomoEfficio/dev-tips/blob/master/Spring%20Test%20MockMvc%EC%9D%98%20%ED%95%9C%EA%B8%80%20%EA%B9%A8%EC%A7%90%20%EC%B2%98%EB%A6%AC.md

반응형
LIST
반응형

If you're new here, you may want to check out the "API Discoverability with Spring and Spring HATEOAS" live Webinar. Thanks for visiting!

I just announced the new Spring 5 modules in REST With Spring:

>> CHECK OUT THE COURSE

1. Overview

In this tutorial, we’ll go over the most common Jackson Exceptions – the JsonMappingException and UnrecognizedPropertyException.

Finally – we’ll briefly discuss Jackson no such method errors.

2. “JsonMappingException: Can not construct instance of”

2.1. The Problem

First – let’s take a look at JsonMappingException: Can not construct instance of.

This exception is thrown if Jackson can’t create an instance of the class – this happens if the class is abstract or it is just an interface.

In the following example – we try to deserialize an instance from class Zoo which has a property animalwith abstract type Animal:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Zoo {
    public Animal animal;
     
    public Zoo() { }
}
 
abstract class Animal {
    public String name;
     
    public Animal() { }
}
 
class Cat extends Animal {
    public int lives;
     
    public Cat() { }
}

When we try to deserialize a JSON String to Zoo instance it throws the “JsonMappingException: Can not construct instance of” as in the following example:

1
2
3
4
5
6
7
8
@Test(expected = JsonMappingException.class)
public void givenAbstractClass_whenDeserializing_thenException()
  throws IOException {
    String json = "{"animal":{"name":"lacy"}}";
    ObjectMapper mapper = new ObjectMapper();
 
    mapper.reader().forType(Zoo.class).readValue(json);
}

The full exception is:

1
2
3
4
5
6
7
8
9
com.fasterxml.jackson.databind.JsonMappingException:
Can not construct instance of org.baeldung.jackson.exception.Animal,
  problem: abstract types either need to be mapped to concrete types,
  have custom deserializer,
  or be instantiated with additional type information
  at
[Source: {"animal":{"name":"lacy"}}; line: 1, column: 2]
(through reference chain: org.baeldung.jackson.exception.Zoo["animal"])
    at c.f.j.d.JsonMappingException.from(JsonMappingException.java:148)

2.2. The Solution

We can solve the problem with a simple annotation – @JsonDeserialize on the abstract class:

1
2
@JsonDeserialize(as = Cat.class)
abstract class Animal {...}

3. JsonMappingException: No suitable constructor

3.1. The Problem

Now – let’s look at the common JsonMappingException: No suitable constructor found for type.

This exception is thrown if Jackson can’t access the constructor.

In the following example – class User doesn’t have a default constructor:

1
2
3
4
5
6
7
8
9
public class User {
    public int id;
    public String name;
 
    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

When we try to deserialize a JSON String to User an Exception “JsonMappingException: No suitable constructor found” is thrown – as in the following example:

1
2
3
4
5
6
7
8
@Test(expected = JsonMappingException.class)
public void givenNoDefaultConstructor_whenDeserializing_thenException()
  throws IOException {
    String json = "{"id":1,"name":"John"}";
    ObjectMapper mapper = new ObjectMapper();
 
    mapper.reader().forType(User.class).readValue(json);
}

The full exception is:

1
2
3
4
5
6
com.fasterxml.jackson.databind.JsonMappingException:
No suitable constructor found for type
[simple type, class org.baeldung.jackson.exception.User]:
 can not instantiate from JSON object (need to add/enable type information?)
 at [Source: {"id":1,"name":"John"}; line: 1, column: 2]
        at c.f.j.d.JsonMappingException.from(JsonMappingException.java:148)

3.2. The Solution

To solve this problem – just add a default constructor as in the following example:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class User {
    public int id;
    public String name;
 
    public User() {
        super();
    }
 
    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

Now when we deserialize – the process will work just fine:

1
2
3
4
5
6
7
8
9
10
11
@Test
public void givenDefaultConstructor_whenDeserializing_thenCorrect()
  throws IOException {
  
    String json = "{"id":1,"name":"John"}";
    ObjectMapper mapper = new ObjectMapper();
 
    User user = mapper.reader()
      .forType(User.class).readValue(json);
    assertEquals("John", user.name);
}

4. JsonMappingException: Root name does not match expected

4.1. The Problem

Next – let’s take a look at JsonMappingException: Root name does not match expected.

This exception is thrown if the JSON doesn’t match exactly what Jackson is looking for; for example, the main JSON could be wrapped as in the following example:

1
2
3
4
5
6
7
8
9
10
@Test(expected = JsonMappingException.class)
public void givenWrappedJsonString_whenDeserializing_thenException()
  throws IOException {
    String json = "{"user":{"id":1,"name":"John"}}";
 
    ObjectMapper mapper = new ObjectMapper();
    mapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE);
 
    mapper.reader().forType(User.class).readValue(json);
}

The full exception is:

1
2
3
4
5
com.fasterxml.jackson.databind.JsonMappingException:
Root name 'user' does not match expected ('User') for type
 [simple type, class org.baeldung.jackson.dtos.User]
 at [Source: {"user":{"id":1,"name":"John"}}; line: 1, column: 2]
   at c.f.j.d.JsonMappingException.from(JsonMappingException.java:148)

4.2. The Solution

We can solve this problem using the annotation @JsonRootName – as in the following example:

1
2
3
4
5
@JsonRootName(value = "user")
public class UserWithRoot {
    public int id;
    public String name;
}

When we try to deserialize the wrapped JSON – it works correctly:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void
  givenWrappedJsonStringAndConfigureClass_whenDeserializing_thenCorrect()
  throws IOException {
  
    String json = "{"user":{"id":1,"name":"John"}}";
 
    ObjectMapper mapper = new ObjectMapper();
    mapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE);
 
    UserWithRoot user = mapper.reader()
      .forType(UserWithRoot.class)
      .readValue(json);
    assertEquals("John", user.name);
}

5. JsonMappingException: No serializer found for class

5.1. The Problem

Now – let’s take a look at JsonMappingException: No serializer found for class.

This exception is thrown if you try to serialize an instance while its properties and their getters are private.

In the following example – we try to serialize a “UserWithPrivateFields“:

1
2
3
4
public class UserWithPrivateFields {
    int id;
    String name;
}

When we try to serialize an instance of “UserWithPrivateFields” – an Exception “JsonMappingException: No serializer found for class” is thrown as in the following example:

1
2
3
4
5
6
7
8
@Test(expected = JsonMappingException.class)
public void givenClassWithPrivateFields_whenSerializing_thenException()
  throws IOException {
    UserWithPrivateFields user = new UserWithPrivateFields(1, "John");
 
    ObjectMapper mapper = new ObjectMapper();
    mapper.writer().writeValueAsString(user);
}

The full exception is:

1
2
3
4
5
com.fasterxml.jackson.databind.JsonMappingException:
No serializer found for class org.baeldung.jackson.exception.UserWithPrivateFields
 and no properties discovered to create BeanSerializer
(to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) )
  at c.f.j.d.ser.impl.UnknownSerializer.failForEmpty(UnknownSerializer.java:59)

5.2. The Solution

We can solve this problem by configuring the ObjectMapper visibility – as in the following example:

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void givenClassWithPrivateFields_whenConfigureSerializing_thenCorrect()
  throws IOException {
  
    UserWithPrivateFields user = new UserWithPrivateFields(1, "John");
 
    ObjectMapper mapper = new ObjectMapper();
    mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
 
    String result = mapper.writer().writeValueAsString(user);
    assertThat(result, containsString("John"));
}

Or using the annotation @JsonAutoDetect – as in the following example:

1
2
@JsonAutoDetect(fieldVisibility = Visibility.ANY)
public class UserWithPrivateFields { ... }

Of course, if we do have the option to modify the source of the class, we can also add in getters for Jackson to use.

6. JsonMappingException: Can not deserialize instance of

6.1. The Problem

Next – let’s take a look at JsonMappingException: Can not deserialize instance of.

This exception is thrown if the wrong type is used while deserializing.

In the following example – we are trying to deserialize a List of User:

1
2
3
4
5
6
7
8
9
@Test(expected = JsonMappingException.class)
public void givenJsonOfArray_whenDeserializing_thenException()
  throws JsonProcessingException, IOException {
  
    String json
      = "[{"id":1,"name":"John"},{"id":2,"name":"Adam"}]";
    ObjectMapper mapper = new ObjectMapper();
    mapper.reader().forType(User.class).readValue(json);
}

The full exception is:

1
2
3
4
5
com.fasterxml.jackson.databind.JsonMappingException:
Can not deserialize instance of
  org.baeldung.jackson.dtos.User out of START_ARRAY token
  at [Source: [{"id":1,"name":"John"},{"id":2,"name":"Adam"}]; line: 1, column: 1]
  at c.f.j.d.JsonMappingException.from(JsonMappingException.java:148)

6.2. The Solution

We can solve this problem by changing the type from User to List<User> – as in the following example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void givenJsonOfArray_whenDeserializing_thenCorrect()
  throws JsonProcessingException, IOException {
  
    String json
      = "[{"id":1,"name":"John"},{"id":2,"name":"Adam"}]";
    
    ObjectMapper mapper = new ObjectMapper();
    List<User> users = mapper.reader()
      .forType(new TypeReference<List<User>>() {})
      .readValue(json);
 
    assertEquals(2, users.size());
}

7. UnrecognizedPropertyException

7.1. The Problem

Now – let’s see the UnrecognizedPropertyException.

This exception is thrown if there is an unknown property in the JSON String while deserializing.

In the following example – we try to deserialize a JSON String with extra property “checked“:

1
2
3
4
5
6
7
8
9
@Test(expected = UnrecognizedPropertyException.class)
public void givenJsonStringWithExtra_whenDeserializing_thenException()
  throws IOException {
  
    String json = "{"id":1,"name":"John", "checked":true}";
 
    ObjectMapper mapper = new ObjectMapper();
    mapper.reader().forType(User.class).readValue(json);
}

The full exception is:

1
2
3
4
5
6
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException:
Unrecognized field "checked" (class org.baeldung.jackson.dtos.User),
 not marked as ignorable (2 known properties: "id", "name"])
 at [Source: {"id":1,"name":"John", "checked":true}; line: 1, column: 38]
 (through reference chain: org.baeldung.jackson.dtos.User["checked"])
  at c.f.j.d.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:51)

7.2. The Solution

We can solve this problem by configuring the ObjectMapper – as in the following example:

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void givenJsonStringWithExtra_whenConfigureDeserializing_thenCorrect()
  throws IOException {
  
    String json = "{"id":1,"name":"John", "checked":true}";
 
    ObjectMapper mapper = new ObjectMapper();
    mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
 
    User user = mapper.reader().forType(User.class).readValue(json);
    assertEquals("John", user.name);
}

Or we can use the annotation @JsonIgnoreProperties:

1
2
@JsonIgnoreProperties(ignoreUnknown = true)
public class User {...}

8. JsonParseException: Unexpected character (”’ (code 39))

8.1. The Problem

Next – let’s discuss JsonParseException: Unexpected character (”’ (code 39)).

This exception is thrown if the JSON String to be deserialized contains single quotes instead of double quotes.

In the following example – we try to deserialize a JSON String containing single quotes:

1
2
3
4
5
6
7
8
9
10
@Test(expected = JsonParseException.class)
public void givenStringWithSingleQuotes_whenDeserializing_thenException()
  throws JsonProcessingException, IOException {
  
    String json = "{'id':1,'name':'John'}";
    ObjectMapper mapper = new ObjectMapper();
 
    mapper.reader()
      .forType(User.class).readValue(json);
}

The full exception is:

1
2
3
4
5
com.fasterxml.jackson.core.JsonParseException:
Unexpected character (''' (code 39)):
  was expecting double-quote to start field name
  at [Source: {'id':1,'name':'John'}; line: 1, column: 3]
  at c.f.j.core.JsonParser._constructError(JsonParser.java:1419)

8.2. The Solution

We can solve this by configuring the ObjectMapper to allow single quotes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void
  givenStringWithSingleQuotes_whenConfigureDeserializing_thenCorrect()
  throws JsonProcessingException, IOException {
  
    String json = "{'id':1,'name':'John'}";
 
    JsonFactory factory = new JsonFactory();
    factory.enable(JsonParser.Feature.ALLOW_SINGLE_QUOTES);
    ObjectMapper mapper = new ObjectMapper(factory);
 
    User user = mapper.reader().forType(User.class)
      .readValue(json);
  
    assertEquals("John", user.name);
}

9. Jackson NoSuchMethodError

Finally – let’s quickly discuss the Jackson “No such method” errors.

When java.lang.NoSuchMethodError Exception is thrown, it is usually because you have multiple (and incompatible) versions of Jackson jars on your classpath.

The full exception is:

1
2
3
java.lang.NoSuchMethodError:
com.fasterxml.jackson.core.JsonParser.getValueAsString()Ljava/lang/String;
 at c.f.j.d.deser.std.StringDeserializer.deserialize(StringDeserializer.java:24)

10. Conclusion

In this article, we did a deep dive in the most common Jackson problems – exceptions and errors, looking at the potential causes and at the solutions for each one.

The implementation of all these examples and code snippets can be found on Github – this is a Maven-based project, so it should be easy to import and run as it is.




출처 : http://www.baeldung.com/jackson-exception

반응형
LIST

+ Recent posts