Wie teste ich, ob der JSON-Pfad kein bestimmtes Element enthält oder ob das Element null ist?

77

Ich habe einige einfache Unit-Test-Routinen für eine einfache Spring-Webanwendung geschrieben. Wenn ich eine @ JsonIgnore-Annotation zu einer Getter-Methode einer Ressource hinzufüge, enthält das resultierende JSON-Objekt nicht das entsprechende JSON-Element. Wenn meine Unit-Test-Routine versucht zu testen, ob dies null ist (was das erwartete Verhalten für meinen Fall ist, ich möchte nicht, dass das Kennwort im JSON-Objekt verfügbar ist), tritt bei der Testroutine eine Ausnahme auf:

java.lang.AssertionError: Kein Wert für JSON-Pfad: $ .password, Ausnahme: Keine Ergebnisse für Pfad: $ ['password']

Dies ist die Unit-Test-Methode, die ich geschrieben habe. Dabei wurde das Feld 'password' mit der Methode is (nullValue ()) getestet:

@Test
public void getUserThatExists() throws Exception {
    User user = new User();
    user.setId(1L);
    user.setUsername("zobayer");
    user.setPassword("123456");

    when(userService.getUserById(1L)).thenReturn(user);

    mockMvc.perform(get("/users/1"))
            .andExpect(jsonPath("$.username", is(user.getUsername())))
            .andExpect(jsonPath("$.password", is(nullValue())))
            .andExpect(jsonPath("$.links[*].href", hasItem(endsWith("/users/1"))))
            .andExpect(status().isOk())
            .andDo(print());
}

Ich habe es auch mit jsonPath () versucht. Exists (), das eine ähnliche Ausnahme erhält, die besagt, dass der Pfad nicht existiert. Ich teile einige weitere Codefragmente, damit die gesamte Situation besser lesbar wird.

Die Controller-Methode, die ich teste, sieht ungefähr so ​​aus:

@RequestMapping(value="/users/{userId}", method= RequestMethod.GET)
public ResponseEntity<UserResource> getUser(@PathVariable Long userId) {
    logger.info("Request arrived for getUser() with params {}", userId);
    User user = userService.getUserById(userId);
    if(user != null) {
        UserResource userResource = new UserResourceAsm().toResource(user);
        return new ResponseEntity<>(userResource, HttpStatus.OK);
    } else {
        return new ResponseEntity<>(HttpStatus.NOT_FOUND);
    }
}

Ich verwende Spring Hateos Resource Assembler zum Konvertieren von Entitäten in Ressourcenobjekte. Dies ist meine Ressourcenklasse:

public class UserResource extends ResourceSupport {
    private Long userId;
    private String username;
    private String password;

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    @JsonIgnore
    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

Ich verstehe, warum dies eine Ausnahme bekommt, auch in gewisser Weise ist der Test erfolgreich, dass es das Passwortfeld nicht finden konnte. Ich möchte diesen Test jedoch ausführen, um sicherzustellen, dass das Feld nicht vorhanden ist oder, falls vorhanden, einen Nullwert enthält. Wie kann ich das erreichen?

Es gibt einen ähnlichen Beitrag im Stapelüberlauf: Hamcrest mit MockMvc: Überprüfen Sie, ob der Schlüssel vorhanden ist, der Wert jedoch möglicherweise null ist

In meinem Fall ist das Feld möglicherweise auch nicht vorhanden.

Dies sind die Versionen der Testpakete, die ich verwende:

    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-core</artifactId>
        <version>2.6.1</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-annotations</artifactId>
        <version>2.6.1</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.6.1</version>
    </dependency>
    <dependency>
        <groupId>com.jayway.jsonpath</groupId>
        <artifactId>json-path</artifactId>
        <version>2.0.0</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>com.jayway.jsonpath</groupId>
        <artifactId>json-path-assert</artifactId>
        <version>2.0.0</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-all</artifactId>
        <version>1.10.19</version>
        <scope>test</scope>
    </dependency>

Danke im Voraus.

[BEARBEITEN] Um genauer zu sein, müssen Sie beispielsweise einen Test für eine Entität schreiben, bei der Sie wissen, dass einige der Felder null oder leer sein müssen oder gar nicht existieren sollten, und Sie den Code nicht durchgehen, um zu sehen Wenn über der Eigenschaft ein JsonIgnore hinzugefügt wurde. Und Sie möchten, dass Ihre Tests bestanden werden. Wie kann ich das tun?

Bitte zögern Sie nicht, mir zu sagen, dass dies überhaupt nicht praktikabel ist, aber dennoch schön zu wissen wäre.

[EDIT] Der obige Test ist mit den folgenden älteren JSON-Pfad-Abhängigkeiten erfolgreich:

    <dependency>
        <groupId>com.jayway.jsonpath</groupId>
        <artifactId>json-path</artifactId>
        <version>0.9.1</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>com.jayway.jsonpath</groupId>
        <artifactId>json-path-assert</artifactId>
        <version>0.9.1</version>
        <scope>test</scope>
    </dependency>

[BEARBEITEN] Es wurde ein Quickfix gefunden, der mit der neuesten Version von jayway.jasonpath funktioniert, nachdem die Dokumentation des json path matcher von spring gelesen wurde.

.andExpect(jsonPath("$.password").doesNotExist())
Zobayer Hasan
quelle
Vielen Dank für Ihre letzte "EDIT". Das .doesNotExist()war was ich suche.
James

Antworten:

159

Ich hatte das gleiche Problem mit der neueren Version. Mir scheint, dass die Funktion doesNotExist () überprüft, ob der Schlüssel nicht im Ergebnis enthalten ist:

.andExpect(jsonPath("$.password").doesNotExist())
benezra444
quelle
1
Das habe ich getan. Schauen Sie sich die letzte Zeile meiner Frage an. [Frage zuletzt bearbeitet am 4. September 15 um 14:55]
Zobayer Hasan
1
Wenn Sie AssertJ verwenden (z. B. in einer Spring Boot-App), können Sie dies auf diese Weise überprüfenassertThat(this.json.write(entity)).doesNotHaveJsonPathValue("@.keyl");
Adam Boczek,
Um zu überprüfen, ob im json keine Eigenschaften vorhanden sind (mit anderen Worten, der json ist ein leeres Objekt {}.andExpect(jsonPath("$.*").doesNotExist())
:)
3

@JsonIgnore verhält sich wie erwartet und erzeugt kein Kennwort in der json-Ausgabe. Wie können Sie also erwarten, etwas zu testen, das Sie explizit von der Ausgabe ausschließen?

Die Linie:

.andExpect(jsonPath("$.property", is("some value")));

oder sogar ein Test, dass die Eigenschaft null ist:

.andExpect(jsonPath("$.property").value(IsNull.nullValue()));

entsprechen einem json wie:

{
...
"property": "some value",
...
}

wo der wichtige Teil die linke Seite ist, ist das die Existenz von "Eigentum":

Stattdessen produziert @JsonIgnore das Porperty überhaupt nicht in der Ausgabe, sodass Sie es weder im Test noch in der Produktionsausgabe erwarten können. Wenn Sie die Eigenschaft nicht in der Ausgabe haben möchten, ist dies in Ordnung, aber Sie können sie im Test nicht erwarten. Wenn Sie möchten, dass es in der Ausgabe leer ist (sowohl in prod als auch in test), möchten Sie in der Mitte eine statische Mapper-Methode erstellen, die den Wert der Eigenschaft nicht an das json-Objekt übergibt:

Mapper.mapPersonToRest(User user) {//exclude the password}

und dann wäre Ihre Methode:

@RequestMapping(value="/users/{userId}", method= RequestMethod.GET)
public ResponseEntity<UserResource> getUser(@PathVariable Long userId) {
    logger.info("Request arrived for getUser() with params {}", userId);
    User user = Mapper.mapPersonToRest(userService.getUserById(userId));
    if(user != null) {
        UserResource userResource = new UserResourceAsm().toResource(user);
        return new ResponseEntity<>(userResource, HttpStatus.OK);
    } else {
        return new ResponseEntity<>(HttpStatus.NOT_FOUND);
    }
}

Wenn Sie zu diesem Zeitpunkt erwarten, dass Mapper.mapPersonToRest einen Benutzer mit einem Nullkennwort zurückgibt, können Sie einen normalen Unit-Test für diese Methode schreiben.

PS Natürlich ist das Passwort in der DB verschlüsselt, oder? ;)

Paolof76
quelle
Ja, JsonIgnore arbeitet wie erwartet. Was wäre also die beste Vorgehensweise zum Testen von Entitäten mit JsonIgnore in einigen Bereichen? Angenommen, Sie möchten einen Test schreiben, wissen, welche Felder leer oder null sein sollten (oder gar nicht existieren sollten), aber Sie werden den Quellcode nicht öffnen und lesen, ob tatsächlich eine JsonIgnore-Annotation darüber vorhanden ist. und natürlich möchten Sie, dass Ihr Test bestanden wird und nicht mit einer Ausnahme fehlschlägt. Oh und natürlich ist das Passwort gehasht. Nicht dass es darauf ankommt, nur ein Testprojekt.
Zobayer Hasan
Was Sie tun möchten, ist darauf zu achten, dass das Objekt UserResource kein Passwort zurückgibt, oder? Angenommen, Sie haben die neue UserResourceAsm (). ToResource (Benutzer), die den Benutzer zurückgibt, richtig? In diesem Fall sollten Sie nicht auf SpringMVC-Ebene testen, sondern nur einen normalen Komponententest durchführen, bei dem überprüft wird, ob user.getPassword () null ist. Hoffe das klären!
Paolof76
Ich habe ein Beispiel hinzugefügt, damit Sie verstehen, was ich meine. Lassen Sie mich sehen, wenn Sie weitere Zweifel haben
Paolof76
ja tut es! Der Grund, warum ich das gefragt habe, ist, dass ich in einem Tutorial, dem ich gefolgt bin, etwas Ähnliches gesehen habe. Der Autor des Tutorials verwendete jedoch eine ältere Version von hamcrest / mockito. Könnte dies ein Grund sein, warum es ihm gelungen ist, Testerfolg zu erzielen?
Zobayer Hasan
Ich habe getestet und ich denke, ich bin in Bezug auf die Version korrekt. Mit json-path und json-path-assert Version 0.9.1 besteht mein Test. Der Test auf Null ist auch dann erfolgreich, wenn das Feld nicht vorhanden ist. Bei den neueren Versionen denke ich jedoch, dass das, was Sie in Ihrer Antwort bezüglich der Verwendung von Mapper beschrieben haben, der bevorzugte Weg ist. Ich lerne gerade die Seile für Unit-Tests.
Zobayer Hasan
0

doesNotHaveJsonPath für die Überprüfung, dass es nicht in json Körper ist

M. Sinan Şahin
quelle
0

Ich wollte den gleichen Code wiederverwenden, den ich zum Testen des bereitgestellten Parameters verwende und der fehlt, und das habe ich mir ausgedacht

  @Test
  void testEditionFoundInRequest() throws JsonProcessingException {
    testEditionWithValue("myEdition");
  }

  @Test
  void testEditionNotFoundInRequest() {
    try {
      testEditionWithValue(null);
      throw new RuntimeException("Shouldn't pass");
    } catch (AssertionError | JsonProcessingException e) {
      var msg = e.getMessage();
      assertTrue(msg.contains("No value at JSON path"));
    }
  }


  void testEditionWithValue(String edition) {   
   var HOST ="fakeHost";
   var restTemplate = new RestTemplate();
   var myRestClientUsingRestTemplate = new MyRestClientUsingRestTemplate(HOST, restTemplate);
   MockRestServiceServer mockServer;
   ObjectMapper objectMapper = new ObjectMapper();
   String id = "userId";
   var mockResponse = "{}";

   var request = new MyRequest.Builder(id).edition(null).build();
   mockServer = MockRestServiceServer.bindTo(restTemplate).bufferContent().build();

   mockServer
        .expect(method(POST))

        // THIS IS THE LINE I'd like to say "NOT" found
        .andExpect(jsonPath("$.edition").value(edition))
        .andRespond(withSuccess(mockResponse, APPLICATION_JSON));

    var response = myRestClientUsingRestTemplate.makeRestCall(request);
  } catch (AssertionError | JsonProcessingException e) {
    var msg = e.getMessage();
    assertTrue(msg.contains("No value at JSON path"));
  }
Brad Parks
quelle
0

Es gibt einen Unterschied zwischen der Eigenschaft, die vorhanden ist, aber einen nullWert hat, und der Eigenschaft, die überhaupt nicht vorhanden ist.

Wenn der Test nur fehlschlagen sollte, wenn kein nullWert vorhanden ist, verwenden Sie:

.andExpect(jsonPath("password").doesNotExist())

Wenn der Test fehlschlagen sollte, sobald die Eigenschaft vorhanden ist, auch mit einem nullWert, verwenden Sie:

.andExpect(jsonPath("password").doesNotHaveJsonPath())
Wim Deblauwe
quelle