2014-04-04 15 views
8

Wpadłem na ten problem podczas testowania kontrolera Spring za pomocą MockMvc, Mockito i Jacksona, więc zrobiłem prostą klasę, aby przetestować zachowanie Jacksona. Używam jackson-databind: 2.3.1 i mockito-core: 1.9.5.Nieskończona rekursja podczas serializacji obiektów z Jackson i Mockito

Biorąc pod uwagę tę klasę:

import com.fasterxml.jackson.core.JsonProcessingException; 
import com.fasterxml.jackson.databind.ObjectMapper; 
import java.io.Serializable; 
import static org.mockito.Mockito.mock; 
import static org.mockito.Mockito.when; 

public class Person implements Serializable { 
    private String name; 
    private int age; 

    // Public getters and setters... 

    public static void main(String[] args) { 
     String name = "Bob"; 
     int age = 21; 
     ObjectMapper objectMapper = new ObjectMapper(); 

     // attempt serialization with real object 
     Person person = new Person(); 
     person.setName(name); 
     person.setAge(age); 
     try { 
      System.out.println(objectMapper.writeValueAsString(person)); 
     } catch (JsonProcessingException e) { 
      e.printStackTrace(); 
      System.err.println("Failed to serialize real object"); 
     } 

     // attempt serialization with mock object 
     Person mockPerson = mock(Person.class); 
     when(mockPerson.getName()).thenReturn(name); 
     when(mockPerson.getAge()).thenReturn(age); 
     try { 
      System.out.println(objectMapper.writeValueAsString(mockPerson)); 
     } catch (JsonProcessingException e) { 
      e.printStackTrace(); 
      System.err.println("Failed to serialize mock object."); 
     } 
    } 

Jackson ma problemu szeregowania rzeczywistego obiektu, jednak będzie ona rzucać JsonMappingException kiedy próbuje serializować szydzili obiekt. Debugowanie kodu powoduje wielokrotne wywoływanie serializeFields (bean, jgen, provider), utknięcie w wewnętrznych właściwościach Mockito.

Moje pytanie brzmi: czy mimo to zmusić Jacksona do użycia metod gettera? Próbowałem @JsonIgnoreProperties na klasie, @JsonIgnore na polach i @JsonProperty na metodach (w różnych kombinacjach, bez powodzenia). Czy muszę napisać własny niestandardowy serializator?

Dzięki!

+0

Gdzie są Twoje metody gettera? –

+0

Są w kodzie! Właśnie je wykluczyłem dla czytelności - po prostu zwracają wartość prywatnego pola w tym przykładzie. –

+0

jackson użyje metody getter, ale te muszą być zgodne z konwencjami nazewnictwa –

Odpowiedz

5

Oto rozwiązanie, które będzie pracować dla Ciebie konkretnym przypadku:

Przede wszystkim trzeba stworzyć PersonMixin ponieważ nie można dodać wymagane adnotacje do makiety.

import com.fasterxml.jackson.annotation.JsonAutoDetect; 
import com.fasterxml.jackson.annotation.JsonProperty; 


@JsonAutoDetect(getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE) 
public interface PersonMixin { 

    @JsonProperty 
    String getName(); 

    @JsonProperty 
    Integer getAge(); 
} 

Teraz użyć mapowania obiektów jak in poniższy kod, a otrzymasz taki sam wynik jak przy prawdziwy obiekt, który serializacji:

Person mockPerson = mock(Person.class); 
when(mockPerson.getName()).thenReturn(name); 
when(mockPerson.getAge()).thenReturn(age); 
objectMapper.addMixInAnnotations(Person.class, PersonMixin.class); 
try { 
    System.out.println(objectMapper.writeValueAsString(mockPerson)); 
} catch (JsonProcessingException e) { 
    e.printStackTrace(); 
    System.err.println("Failed to serialize mock object."); 
} 
+0

To zadziałało, dzięki! Czy to jest wyłącznie ograniczenie Mockito? W swojej dokumentacji nie natknąłem się na makiety zachowujące adnotacje ich klas. –

+1

@MatthewSerrano Twoje powitanie! Właściwie twoje prawo, nie potrzebujesz mixina! Jeśli umieścisz adnotacje na osobie, będzie działało również na pozorny obiekt! Nawet o tym nie myślałem, ponieważ uważałem za oczywiste, że mockito nie zachowa adnotacji, podczas gdy w rzeczywistości to robi! Jednak nadal bym się trzymał adnotacji na mixinie, ponieważ adnotacje są potrzebne tylko do prób, a zatem wprowadzenie ich do obiektu biznesowego wprowadziłoby złożoność, która jest potrzebna tylko w celu kpienia z – geoand

+0

Dzięki, działa rozwiązanie! –

2

Oto mój ObjectMapper że to załatwili bez konieczności dla mixinów.

Mapper ignoruje wszystkich członków, którzy mają "Mockito" gdzieś w swoich nazwach.

To rozwiązanie pozwala uniknąć miksowania każdego serializowanego obiektu lub kodu komentarza, który może być niedostępny.

Wykonanie poniższego testu kończy się sukcesem z wynikiem {"name":"Jonh"}.

package test; 

import com.fasterxml.jackson.core.JsonProcessingException; 
import com.fasterxml.jackson.databind.ObjectMapper; 
import com.fasterxml.jackson.databind.introspect.AnnotatedMember; 
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector; 
import org.mockito.Mockito; 

public class AppTest extends Mockito { 

    public void testApp() throws JsonProcessingException { 
     ObjectMapper mapper = new ObjectMapper(); 
     mapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector() { 

      @Override 
      public boolean hasIgnoreMarker(final AnnotatedMember m) { 
       return super.hasIgnoreMarker(m) || m.getName().contains("Mockito"); 
      } 
     }); 

     final String name = "Jonh"; 

     Person mockPerson = mock(Person.class); 
     when(mockPerson.getName()).thenReturn(name); 
     System.out.println(mapper.writeValueAsString(mockPerson)); 
    } 


    public static class Person { 

     private String name; 

     public String getName() { 
      return name; 
     } 

     public void setName(String name) { 
      this.name = name; 
     } 
    } 
}