REST-assured: полезные советы

    В данной статье я собрал полезные советы по использованию REST-assured, одной из самых распространенных Java-библиотек для автоматизации тестирования REST-API.

    Все примеры жизненные, они собраны из моей практики проведения code-review в более чем 50 проектах с автотестами.

    Выносите end-point'ы в отдельное место


    Казалось бы, что это очевидно. Но нет, довольно часто приходится видеть код с захардкоженными end-point'ми в запросе.

    Лучше всего выносить end-point'ы в статические константы финального класса. При этом стоит избегать антипаттерн «константный интерфейс» — это плохая практика.

    Не забывайте, что REST-assured позволяет выносить параметры пути, например:

    public final class EndPoints {
        public static final String users = "/users/{id}";
    ...
    }
    
    given().pathParams("id", someId).get(EndPoints.users)...;
    
    // или так
    
    given().get(EndPoints.users, someId)....
    

    Также, если во многих запросах вы используете один и тот же базовый путь, то будет хорошей практикой вынести его в отельную константу и передавать в basePath, например:

    // имеем следующий url приложения http://host:port/appname/rest/someEndpoints
    
    private static final basePath = "/appname/rest/";
    
    ..
    
    // можем задать базовый путь на глобальном уровне,
    // он будет применяться ко всем запросам:
    RestAssured.basePath = basePath;
    
    // или на уровне одного запроса:
    given().basePath(basePath)...
    
    // или на уровне спецификации, но об этом далее
    

    То же самое применимо к хосту и порту тестируемого приложения.

    ContentType/Accept


    Данные заголовки используются практически во всех HTTP-запросах. Авторы REST-assured, понимая это, сделали возможным их установку через вызов специальных методов:

    // плохая практика написания:
    given().header("content-type", "application/json").header("accept", "application/json")...;
    
    // хорошая практика написания:
    given().contentType(ContentType.JSON).accept(ContentType.JSON)...;
    

    Хорошей практикой будет установить данные заголовки в спецификации или на глобальном уровне. Это повысит читабельность вашего кода.

    StatusCode и т.п.


    REST-assured предоставляет удобный синтаксис для проведения проверки каждой составляющей HTTP-ответа, однако на практике продолжаешь встречать подобный код:

    // плохая практика написания:
    Response response = given()...when().get(someEndpoint);
    Assert.assertEquals(200, response.then().extract().statusCode());
    
    // хорошая практика написания:
    given()...when().get(someEndpoint).then().statusCode(200);
    

    Используйте спецификации


    Дублирование кода — это плохо. Используйте спецификации для уменьшения дублирования. В REST-assured можно создавать спецификации как для запроса, так и для ответа. В спецификацию запроса выносим всё, что может быть продублировано в запросах.

    RequestSpecification requestSpec = new RequestSpecBuilder()
        .setBaseUri("http://localhost")
        .setPort(8080)
        .setAccept(ContentType.JSON)
        .setContentType(ContentType.ANY)
    ...
        .log(LogDetail.ALL)
        .build();
    
    // можно задать одну спецификацию для всех запросов:
    RestAssured.requestSpecification = requestSpec;
    
    // или для отдельного:
    given().spec(requestSpec)...when().get(someEndpoint);
    

    В спецификацию ответа выносим все проверки, которые дублируются от запроса к запросу.

    ResponseSpecification responseSpec = new ResponseSpecBuilder()
        .expectStatusCode(200)
        .expectBody(containsString("success"))
        .build();
    
    // можно задать одну спецификацию для всех ответов:
    RestAssured.responseSpecification = responseSpec;
    
    // или для отдельного:
    given()...when().get(someEndpoint).then().spec(responseSpec)...;
    

    Можно создавать несколько спецификаций для разных типов запросов/ответов и использовать в нужной ситуации.

    Не пишите свои костыли для преобразования объектов


    Не стоит преобразовывать свои POJO в JSON при помощи Jackson ObjectMapper'а, а потом полученную строку передавать в тело запроса. REST-assured прекрасно справляется с этой задачей. Для этого используется всё тот же Jackson или Gson, в зависимости от того, что находится в classpath. Для преобразования в XML используется JAXB. Исходный формат определяется автоматически по значению Content-Type.

    given().contentType(ContentType.JSON).body(somePojo)
        .when().post(EndPoints.add)
        .then()
        .statusCode(201);
    
    // то же самое работает и в обратную сторону:
    SomePojo pojo = given().
        .when().get(EndPoints.get)
        .then().extract().body().as(SomePojo.class);
    

    Кроме того REST-assured прекрасно справляется с преобразованием HashMap в JSON и обратно.

    Используйте всю мощь Groovy


    Сама библиотека REST-assured написана на Groovy и позволяет вам применять различные методы из Groovy к полученному JSON/XML ответу. Например:

    // методы find, findAll применяются к коллекции для поиска первого и всех вхождений, метод collect для  создания новой коллекции из найденных результатов. 
    
    // переменная it создается неявно и указывает на текущий элемент коллекции
    Map<String, ?> map = get(EndPoints.anyendpoint).path("rootelement.find { it.title =~ 'anythingRegExp'}");
    
    // можете явно задать название переменной, указывающей на текущий элемент
    Map<String, ?> map = get(EndPoints.anyendpoint).path("rootelement.findAll { element -> element.title.length() > 4 }");
    
    // вы можете использовать методы sum, max, min для суммирования всех значений коллекции, а также поиска максимального и минимально значения
    
    String expensiveCar = get(EndPoints.cars).path("cars.find { it.title == 'Toyota Motor Corporation'}.models.max { it.averagePrice }.title");
    

    Использование методов из Groovy позволяет сильно сократить количество кода, написанного вами для поиска необходимого значения из ответа.

    На этом всё, если у вас есть еще советы и примеры пишите их в комментариях.
    Поделиться публикацией
    Комментарии 13
      0
      Было бы хорош еще и статью по основам
      REST-assured, одной из самых распространенных Java-библиотек для автоматизации тестирования REST-API.
        +1
        Порог вхождения в автоматизацию тестирования с использованием библиотеки REST-assured невероятно низкий. Именно поэтому эта библиотека такая распространенная, но это же и её бич — многие заканчивают изучение на статьях «по основам». В интернете много таких статей, но я бы порекомендовал почитать официальную документацию, которая к слову очень хорошая.
        0
        Спасибо, но очень кратко. Есть пример образцового проекта с использованием restAssured?
          +1
          К сожалению, пример образцового проекта показать не смогу. Да и нет, наверное, его. Видел много различных проектов, с различной архитектурой, и у каждого были свои плюсы и минусы. Например, можно практически всё описать зеркально с тестируемого приложения. Вынести контроллеры в отдельные классы, там собрать вызовы методов этих контроллеров, в тестах же оставить только проверки. Если же в тестируемом приложении используется flow, то можно попробовать пойти от описания шагов этого flow. А может быть вообще стоит всё описать в тестовых методах и не париться. :)
            0
            Да согласен, каждый делает по своему)
            Еще чем нравится REST Assured это очень простая реализация проверки по json schema. К сожалению на рабочем проекте используется Retrofit, и там валидацию по схеме ввернуть так легко не вышло.
          0
          Про проверки. Если хочется проверить statusCode, но и что-то из body, все равно же не уложиться в конструкцию given-when-then?

          «then().statusCode(200);»
          Все равно придется заводить какие-то переменные и использовать assert?
            0
            Можно, нужно продолжать цепочку вызовов, например так:
            given().cookie(«key», «value»)
            .when().get(«someUrl»)
            .then().statusCode(200).body(matchesJsonSchemaInClasspath(«schema.json»)).cookie(«newCookie»);
            В данном случае будет 3 проверки:
            1. Статус код соответствует 200;
            2. Тело ответа соответствует Json Schema;
            3. В ответ пришел новый куки с ключом «newCookie».
            Единственный минус, что это не SoftAssert и если свалится на первой проверке, остальные не будут проверяться.
            Но это очень редко требуется.
            Для лучшей читабельности можно использовать вызов союза and():
            ...then().statusCode(200)
            .and()
            .body(matchesJsonSchemaInClasspath(«schema.json»))
            .and()
            .cookie(«newCookie»);
            Терминальными методами являются только методы, вызываемые после метода extract():
            ...then().statusCode(200).extract().body().asString();
            0
            А как вы проверяете правильную сортировку данных?
              0
              Правильно ли я понимаю, что данный вопрос не относится к REST-assured?
              Сортировку лучше всего проверять зная то, что сортируется и как оно должно выглядеть после сортировки. Лучше всего это делать на уровне Unit-тестов.
              Писать свою сортировку для проверки другой сортировки (если вы на это намекаете), мне кажется плохой вариант, но возможно, кто-то делает именно так.
              Я бы заполнил нужную коллекцию известными мне данными и после сортировки проверил, что они имеют ожидаемый порядок.
              0

              Начал недавно работать с этой библиотекой и вскоре столкнулся с таким вопросом: стоит ли десерилизовать ответ в класс или можно обойтись матчерами Hamcrest прямо в методе body()?
              Можете рассказать на этот счёт?

                0
                Если ваша задача только осуществить проверку полученного ответа, то необходимости в десериализации точно нет. REST-assured и Hamcrest позволяют осуществить любую проверку. Если же вам необходимо в дальнейшем каким-то образом работать с сущностью, полученной в ответе, то можно воспользоваться десериализацией.
                  0

                  Пока справляюсь средствами RestAssured и Hamcrest. Но думаю, может, я чего-то упускаю, не делая десирелиазацию.


                  А каким образом с сущностью можно работать далее, выполнив десериализацию? Что обычно делают?

                    +1
                    Десериализация может понадобиться, например, для осуществления долгосрочного хранения или преобразования сущности в другой формат.

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

              Самое читаемое