2014-04-04 14 views
8

Ich stieß auf dieses Problem beim Testen eines Spring-Controllers mit MockMvc, Mockito und Jackson, also machte ich eine einfache Klasse, um zu testen, wie Jackson sich verhält. Ich benutze Jackson-Databind: 2.3.1 und Mockito-Core: 1.9.5.Unendliche Rekursion beim Serialisieren von Objekten mit Jackson und Mockito

diese Klasse Gegeben:

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 hat kein Problem, das reale Objekt Serialisierung, aber es wird eine JsonMappingException werfen, wenn es die verspottete Objekt serialisiert werden versucht. Beim Debuggen durch den Code ruft es serializeFields (bean, jgen, provider) wiederholt auf und bleibt auf den internen Mockito-Eigenschaften stecken.

Also, meine Frage ist: Gibt es sowieso Jackson zu zwingen, die Getter-Methoden zu verwenden? Ich habe versucht @ JsonIgnoreProperties für die Klasse, @ JsonIgnore für die Felder und @ JsonProperty für die Methoden (in verschiedenen Kombinationen, ohne Erfolg). Oder muss ich meinen eigenen benutzerdefinierten Serializer schreiben?

Danke!

+0

Wo sind Ihre Getter-Methoden? –

+0

Sie sind im Code! Ich habe sie einfach aus Gründen der Lesbarkeit ausgeschlossen - sie geben einfach den Wert des privaten Feldes in diesem Beispiel zurück. –

+0

jackson wird die Getter-Methode verwenden, aber diese müssen sich an die Namenskonventionen halten –

Antwort

5

Hier ist eine Lösung, die für Sie bestimmten Fall funktioniert:

Zunächst einmal benötigen Sie einen PersonMixin erstellen, da Sie nicht die erforderlichen Anmerkungen zu den mock hinzufügen können.

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(); 
} 

Verwenden Sie nun das Objekt Mapper wie die in den folgenden Code, und Sie werden das gleiche Ergebnis wie erhalten, wenn Sie das reale Objekt serialisiert:

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

Das hat funktioniert, danke! Ist das streng eine Mockito-Beschränkung? Ich habe nichts in ihrer Dokumentation über Mocks gefunden, die die Annotationen ihrer Klassen behalten haben. –

+1

@MatthewSerano Herzlich willkommen! Wirklich dein Recht, du brauchst das Mixin nicht! Wenn Sie die Anmerkungen auf Person setzen, wird es auch für das Scheinobjekt funktionieren! Ich habe nicht einmal daran gedacht, weil ich angenommen habe, dass Mockito die Anmerkungen nicht behalten würde, obwohl es tatsächlich so ist! Allerdings würde ich immer noch mit den Anmerkungen auf dem Mixin bleiben, weil die Annotationen nur für den Schein benötigt werden, und würde daher Einführung in das Geschäftsobjekt Komplexität einführen, die nur für den Zweck der Verspottung benötigt wird – geoand

+0

Danke, Lösung funktioniert! –

2

Hier ist meine ObjectMapper, dass es ohne die Notwendigkeit aussortiert für Mixins.

Der Mapper ignoriert alle Mitglieder, die "Mockito" irgendwo in ihrem Namen haben.

Diese Lösung vermeidet, dass für jedes serialisierte Objekt ein Mix-In oder Annotationscode verwendet wird, auf den möglicherweise nicht zugegriffen werden kann.

Der folgende Test ist erfolgreich mit dem Ausgang {"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; 
     } 
    } 
} 
Verwandte Themen