Як перевірити, чи шлях JSON не включає певний елемент, або якщо елемент присутній, він є нульовим?


78

Я писав кілька простих процедур модульного тестування для простого весняного веб-додатку. Коли я додаю анотацію @JsonIgnore для методу отримання ресурсу, отриманий об'єкт json не включає відповідний елемент json. Отже, коли моя підпрограма модульного тесту намагається перевірити, чи є це значення нульовим (що є очікуваною поведінкою для мого випадку, я не хочу, щоб пароль був доступний в об'єкті json), тестова підпрограма запускає виняток:

java.lang.AssertionError: Немає значення для шляху JSON: $ .password, виняток: Немає результатів для шляху: $ ['пароль']

Це метод модульного тестування, який я написав, тестуючи поле 'пароль' методом is (nullValue ()):

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

Я також спробував це за допомогою jsonPath (). Існує (), яке отримує подібне виняток, заявляючи, що шлях не існує. Я ділюсь ще кількома фрагментами коду, щоб вся ситуація стала читабельнішою.

Метод контролера, який я тестую, виглядає приблизно так:

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

Я використовую асемблер ресурсів hateos spring для перетворення сутності в об'єкти ресурсів, і це мій клас ресурсів:

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

Я розумію, чому це отримує виняток, також певним чином, тест успішний, оскільки він не міг знайти поле пароля. Але що я хочу зробити, це запустити цей тест, щоб переконатися, що поле відсутнє, або якщо воно присутнє, воно містить нульове значення. Як я можу цього досягти?

У переповненні стека є подібний пост: Hamcrest з MockMvc: перевірте, чи існує ключ, але значення може бути нульовим

У моєму випадку поле може також не існувати.

Для запису, це версії тестових пакетів, які я використовую:

    <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>

Заздалегідь спасибі.

[РЕДАКТУВАТИ] Якщо бути точнішим, скажімо, ви повинні написати тест для сутності, де ви знаєте, що деякі поля мають бути нульовими або порожніми, або навіть не повинні існувати, і ви насправді не проглядаєте код, щоб побачити якщо зверху властивості додано JsonIgnore. І ви хочете, щоб ваші тести пройшли, як я можу це зробити.

Будь ласка, не соромтеся сказати мені, що це зовсім не практично, але все одно було б приємно це знати.

[РЕДАКТУВАТИ] Наведений вище тест вдався за допомогою наступних старих залежностей json-path:

    <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>

[EDIT] Знайдено швидке виправлення, яке працює з останньою версією jayway.jasonpath після прочитання документації збігового шляху json spring.

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

Дякуємо за ваш останній "РЕДАГУВАТИ". Це .doesNotExist()було те, що я шукав.
Джеймс

Відповіді:


164

У мене була та ж проблема з новішою версією. Мені здається, що функція doesNotExist () перевірить, що ключа немає в результаті:

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

1
ось що я зробив, подивіться на останній рядок мого запитання. [питання востаннє відредаговано 4 вересня '15 о 14:55]
Zobayer Hasan

1
якщо ви використовуєте AssertJ (наприклад, у програмі Spring Boot), це спосіб перевірити цеassertThat(this.json.write(entity)).doesNotHaveJsonPathValue("@.keyl");
Адам Бочек

Крім того, щоб перевірити, чи немає властивостей у json (іншими словами, json є порожнім об'єктом {}.andExpect(jsonPath("$.*").doesNotExist())
:)

3

@JsonIgnore поводиться належним чином, не видаючи пароль у вихідному файлі json, то як би ви могли очікувати тестування чогось, що ви явно виключаєте з результату?

Лінія:

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

або навіть тест, що властивість є нульовим:

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

відповідають json типу:

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

де важливою частиною є ліва сторона, тобто існування "власності":

Натомість @JsonIgnore взагалі не створює властивість у вихідних даних, тому ви не можете очікувати цього ні в тесті, ні у виробничому виданні. Якщо ви не хочете властивість у вихідних даних, це нормально, але ви не можете очікувати цього в тесті. Якщо ви хочете, щоб він був порожнім у вихідних даних (як у prod, так і в тесті), ви хочете створити статичний метод Mapper посередині, який не передає значення властивості об'єкту json:

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

і тоді ваш метод буде таким:

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

На даний момент, якщо ви очікуєте, що Mapper.mapPersonToRest поверне користувачеві нульовий пароль, ви можете написати звичайний модульний тест для цього методу.

PS Звичайно, пароль зашифрований в БД, так? ;)


Так, JsonIgnore працює належним чином. Тож якою буде найкраща практика тестування об’єктів за допомогою JsonIgnore за деякими полями. Припустимо це, скажімо, ви хочете написати тест, ви знаєте, які поля повинні бути порожніми або нульовими (або навіть не повинні існувати), але ви не збираєтеся відкривати вихідний код і читати, чи над ними дійсно є анотація JsonIgnore. і, очевидно, ви хочете, щоб ваш тест пройшов, а не провалився за винятком. Ну і звичайно, пароль хешований. Не те щоб це мало значення, просто тестовий проект.
Zobayer Hasan

Що ви хочете зробити, це подбати про те, щоб об’єкт UserResource не повертав пароль, так? Скажімо, у вас є новий UserResourceAsm (). ToResource (user), який повертає користувача, так? У цьому випадку ви повинні тестувати не на рівні SpringMVC, а просто виконати звичайний модульний тест, який перевіряє user.getPassword () як нуль. Сподіваюся, це уточнити!
Paolof76

Я додав приклад, щоб допомогти вам зрозуміти, що я маю на увазі. Дозвольте подивитися, чи більше сумнівів у вас
Paolof76

так! Причина, по якій я запитав це, полягає в тому, що я побачив щось подібне у навчальному посібнику, за яким я пішов. Але автор підручника використав стару версію hamcrest / mockito. Чи може це бути причиною того, чому йому вдалося досягти успішного тесту?
Zobayer Hasan

Я протестував, і я вважаю, що я правильно вважаю цю версію. З json-path та json-path-assert версії 0.9.1 мій тест проходить. Перевірка на нуль успішна, навіть якщо поле не існує. Однак у нових версіях, я думаю, те, що ви описали у своїй відповіді щодо використання Mapper, є найкращим способом. Я просто вивчаю мотузки для модульного тестування.
Zobayer Hasan


0

Я хотів повторно використати той самий код, який використовую для тестування параметра, що поставляється, і для його відсутності, і ось що я придумав

  @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"));
  }

0

Існує різниця між властивістю, яка присутня, але має nullвартість, і властивістю, яка взагалі не присутня.

Якщо тест повинен провалитися лише тоді, коли є nullзначення, яке не має значення, використовуйте:

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

Якщо тест повинен провалитися, як тільки властивість присутня, навіть зі nullзначенням, використовуйте:

.andExpect(jsonPath("password").doesNotHaveJsonPath())
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.