Resource library

QA How-To

REST Assured Query and Path Parameters Tutorial

Learn REST Assured query and path parameters with runnable Java tests for named routes, filters, pagination, repeated keys, encoding, and error cases.

23 min read | 3,263 words

TL;DR

Use pathParam("name", value) to fill a named route placeholder and queryParam("name", value) to add a URL query value. This tutorial runs both against a local API, then checks repeated keys, encoding, invalid pages, and missing resources.

Key Takeaways

  • Use pathParam for named resource segments and queryParam for filtering, sorting, and pagination.
  • Keep parameter values unencoded so REST Assured can encode reserved characters once.
  • Assert the selected route and decoded query values before relying on status codes alone.
  • Test repeated query keys only when the provider contract defines that wire format.
  • Separate invalid query input from an absent resource in negative tests.
  • Use isolated request specifications and verify real pagination behavior against the provider.

REST Assured Query and Path Parameters let you test a resource address and its optional filters without building URLs by hand. Put a required resource identifier in the route with pathParam, and put search, pagination, or sorting controls in the query string with queryParam. REST Assured substitutes and encodes the values when it sends the request.

This tutorial builds a tiny local HTTP API and tests it with Java, JUnit, and REST Assured. The server echoes the route and decoded query values, so you can see exactly what your test sent. By the end you will also test repeated keys, special characters, invalid input, and a reusable request specification. For broader request construction, see the REST Assured beginner tutorial.

What You Will Build

  • A local book API that starts on a free loopback port inside the test process.
  • A named path parameter test for GET /books/{bookId}.
  • Query parameter tests for search, pagination, sorting, and repeated tag keys.
  • A combined route and query test for a book in a specific shop.
  • Negative tests that distinguish a missing resource from an invalid query value.
  • A small request specification and a parameterized test that are safe to reuse.

Prerequisites

Use JDK 17, Apache Maven 3.9.16, REST Assured 6.0.1, JUnit Jupiter 6.1.3, Maven Compiler Plugin 3.14.1, and Maven Surefire Plugin 3.5.5 for this example. These are published versions; if your organization manages a different version, match its installed dependency set and review compatibility before changing the coordinates. Because the REST Assured 6 jars are compiled for Java 17, an older JDK rejects them at compile time with a class file version error. Install the JDK and Maven through your normal package manager, then check the active binaries:

java -version
mvn -version

Create an empty directory for the tutorial, with pom.xml at its root and Java tests under src/test/java/example/. The server uses the JDK's jdk.httpserver module, so no additional server library or public API account is needed. The Maven build explicitly adds that module for compilation and test execution.

The REST Assured given, when, then guide explains the fluent syntax used below. You can follow this tutorial without that guide because each test includes its complete request and assertion.

Step 1: Create the Maven Project

Create pom.xml with these exact dependency and plugin coordinates. REST Assured appears before JUnit in the dependency list, matching the project's classpath guidance. The compiler targets Java 17; Surefire runs the Jupiter engine and adds jdk.httpserver when the test JVM starts.

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>example</groupId>
  <artifactId>parameter-tutorial</artifactId>
  <version>1.0-SNAPSHOT</version>

  <properties>
    <maven.compiler.release>17</maven.compiler.release>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>io.rest-assured</groupId>
      <artifactId>rest-assured</artifactId>
      <version>6.0.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter</artifactId>
      <version>6.1.3</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.14.1</version>
        <configuration>
          <compilerArgs>
            <arg>--add-modules</arg>
            <arg>jdk.httpserver</arg>
          </compilerArgs>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>3.5.5</version>
        <configuration>
          <argLine>--add-modules jdk.httpserver</argLine>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

A Maven Wrapper is useful when this example becomes part of a team repository: commit the wrapper files and invoke ./mvnw test so CI and laptops use the same Maven distribution. Keep the dependency versions in one reviewed POM or dependency catalog. The command shown here uses plain mvn because the tutorial does not generate wrapper files. A successful dependency download proves the coordinates resolve; it does not prove that your local JDK matches the selected compiler release.

Verify Step 1: Run mvn -q test. Maven should exit successfully after downloading dependencies. It may report that no tests were run; that is expected until Step 3. If the compiler reports an unsupported release, check mvn -version: Maven may be using a different JDK from your shell's java command.

Step 2: Start a Deterministic Local API

Save the following as src/test/java/example/DemoApi.java. The fixture allocates an available port, accepts only GET, and returns JSON describing the decoded route and query values. It gives a known book ID a success response, returns 404 for unknown books, and returns 400 when page is not a positive integer. That makes the later assertions meaningful rather than testing an arbitrary remote service.

package example;

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

final class DemoApi implements AutoCloseable {
  private final HttpServer server;

  private DemoApi(HttpServer server) {
    this.server = server;
  }

  static DemoApi start() throws IOException {
    HttpServer server = HttpServer.create(
        new InetSocketAddress("127.0.0.1", 0), 0);
    server.createContext("/", DemoApi::handle);
    server.start();
    return new DemoApi(server);
  }

  String baseUri() {
    return "http://127.0.0.1:" + server.getAddress().getPort();
  }

  private static void handle(HttpExchange exchange) throws IOException {
    String path = exchange.getRequestURI().getPath();
    if (!"GET".equals(exchange.getRequestMethod())) {
      send(exchange, 405, "{\"error\":\"METHOD_NOT_ALLOWED\"}");
      return;
    }

    Map<String, List<String>> query =
        parseQuery(exchange.getRequestURI().getRawQuery());
    String page = first(query, "page");
    if (!page.isEmpty() && !page.matches("[1-9][0-9]*")) {
      send(exchange, 400, "{\"error\":\"INVALID_PAGE\"}");
      return;
    }

    boolean collection = "/books".equals(path);
    boolean book = "/books/b-42".equals(path);
    boolean shopBook = "/shops/7/books/b-42".equals(path);
    if (!collection && !book && !shopBook) {
      send(exchange, 404, "{\"error\":\"BOOK_NOT_FOUND\"}");
      return;
    }

    List<String> tags = query.getOrDefault("tag", List.of());
    StringBuilder tagJson = new StringBuilder();
    for (String tag : tags) {
      if (tagJson.length() > 0) tagJson.append(',');
      tagJson.append(quoted(tag));
    }
    String json = "{\"path\":" + quoted(path)
        + ",\"q\":" + quoted(first(query, "q"))
        + ",\"page\":" + quoted(page)
        + ",\"size\":" + quoted(first(query, "size"))
        + ",\"sort\":" + quoted(first(query, "sort"))
        + ",\"tags\":[" + tagJson + "]"
        + ",\"rawQuery\":" + quoted(
            exchange.getRequestURI().getRawQuery() == null
                ? "" : exchange.getRequestURI().getRawQuery()) + "}";
    send(exchange, 200, json);
  }

  private static Map<String, List<String>> parseQuery(String raw) {
    Map<String, List<String>> values = new LinkedHashMap<>();
    if (raw == null || raw.isEmpty()) return values;
    for (String pair : raw.split("&")) {
      String[] parts = pair.split("=", 2);
      String key = URLDecoder.decode(parts[0], StandardCharsets.UTF_8);
      String value = parts.length == 2
          ? URLDecoder.decode(parts[1], StandardCharsets.UTF_8) : "";
      values.computeIfAbsent(key, ignored -> new ArrayList<>()).add(value);
    }
    return values;
  }

  private static String first(Map<String, List<String>> values, String key) {
    return values.getOrDefault(key, List.of()).stream()
        .findFirst().orElse("");
  }

  private static String quoted(String value) {
    return "\"" + value.replace("\\", "\\\\")
        .replace("\"", "\\\"")
        .replace("\n", "\\n")
        .replace("\r", "\\r") + "\"";
  }

  private static void send(HttpExchange exchange, int status, String json)
      throws IOException {
    byte[] bytes = json.getBytes(StandardCharsets.UTF_8);
    exchange.getResponseHeaders().set("Content-Type", "application/json");
    exchange.sendResponseHeaders(status, bytes.length);
    try (var output = exchange.getResponseBody()) {
      output.write(bytes);
    }
  }

  @Override
  public void close() {
    server.stop(0);
  }
}

Binding to 127.0.0.1 keeps the fixture on the local machine. Passing port zero asks the operating system for a free port, which avoids collisions when another developer has a service on 8080. The fixture closes the response stream for every request and stops after the test class. If you replace it with a shared environment, do not assume test data is isolated: create known records or use a dedicated account, and clean up records that your test creates.

Verify Step 2: Run mvn -q test again. Compilation should succeed. Maven still has no test method to execute, but it now compiles the local API fixture. Fix compilation errors here before adding REST Assured requests.

Step 3: Send a Named Path Parameter

Create src/test/java/example/ParameterTest.java. This complete test class starts the local server once, uses its allocated port, and stops it when the class finishes. Keep the imports and lifecycle methods as shown: the next steps add methods inside this class, directly above its final closing brace.

package example;

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.jupiter.api.Assertions.assertTrue;

import io.restassured.builder.RequestSpecBuilder;
import io.restassured.specification.RequestSpecification;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

class ParameterTest {
  private static DemoApi api;

  @BeforeAll
  static void startApi() throws Exception {
    api = DemoApi.start();
  }

  @AfterAll
  static void stopApi() {
    api.close();
  }

  @Test
  void namedPathParameterSelectsBook() {
    given()
        .baseUri(api.baseUri())
        .pathParam("bookId", "b-42")
    .when()
        .get("/books/{bookId}")
    .then()
        .statusCode(200)
        .body("path", equalTo("/books/b-42"));
  }
}

The placeholder name in the route must match the name in pathParam. An identifier is a path parameter here because it selects one book. The test checks the route echoed by the server, not merely that some 200 response arrived. Use named placeholders when a route has more than one dynamic segment; they document what each value means and avoid positional mistakes.

For path IDs that contain spaces, Unicode, percent signs, or slashes, review the provider's routing rules before writing an assertion. A slash can be interpreted as another path segment; a provider may prohibit such IDs or require a specific encoding contract. Test an allowed representative ID and assert the returned entity identity. Do not infer correctness solely from a pretty printed URL, since proxies and frameworks may decode or normalize a path before the application reads it.

Verify Step 3: Run mvn -q -Dtest=ParameterTest#namedPathParameterSelectsBook test. The command should exit with status 0. Remove the pathParam line temporarily if you want to see REST Assured report an unresolved placeholder, then restore it before proceeding.

Step 4: Add Search, Pagination, and Sort Query Parameters

Paste this method before the final brace in ParameterTest. A collection route stays "/books"; optional controls follow it as query parameters. The fixture returns each decoded value so an incorrect key or value fails a specific assertion.

@Test
void queryParametersControlBookSearch() {
  given()
      .baseUri(api.baseUri())
      .queryParam("q", "java")
      .queryParam("page", 2)
      .queryParam("size", 10)
      .queryParam("sort", "title,asc")
  .when()
      .get("/books")
  .then()
      .statusCode(200)
      .body("path", equalTo("/books"))
      .body("q", equalTo("java"))
      .body("page", equalTo("2"))
      .body("size", equalTo("10"))
      .body("sort", equalTo("title,asc"));
}

queryParam places the values in the URL query string. The Java integer becomes a textual URL value, which is why the echo assertions compare "2" and "10". The order of query keys is rarely part of an API contract; assert parsed semantics unless a signature algorithm explicitly requires canonical ordering.

Decide how absence differs from an empty value. Omitting q asks for the default collection; sending q= may mean search for an empty string, clear a filter, or fail validation. A null Java argument can have library-specific handling, so write separate tests for omission and a deliberate blank value instead of treating them as interchangeable. If a query key has a default, assert the default result and metadata. Otherwise a test can pass because the server ignored a misspelled key.

Verify Step 4: Run mvn -q -Dtest=ParameterTest#queryParametersControlBookSearch test. A passing result proves the server received the intended search, page, size, and sort values. Change page to zero and observe the fixture return 400; Step 7 will assert that failure deliberately.

Step 5: Combine REST Assured Query and Path Parameters

Use both parameter types when the route selects a resource and the query changes its representation. This test addresses book b-42 within shop 7, then asks for a locale. The fixture does not have locale-specific content, so it can echo only the route and general query fields; use q here as a documented filter for this demonstration.

@Test
void pathAndQueryParametersCanCoexist() {
  given()
      .baseUri(api.baseUri())
      .pathParam("shopId", 7)
      .pathParam("bookId", "b-42")
      .queryParam("q", "hardcover")
  .when()
      .get("/shops/{shopId}/books/{bookId}")
  .then()
      .statusCode(200)
      .body("path", equalTo("/shops/7/books/b-42"))
      .body("q", equalTo("hardcover"));
}

REST Assured substitutes each named segment before sending the request and appends the query controls. Do not put a question mark inside the route template when the builder is already adding query parameters. The resulting address has one path and one query string. If a real endpoint does not permit a q filter on an individual book, replace it with a supported option such as locale or include, then assert the response behavior that option promises.

In a tenant-scoped API, also test that a book known to shop 7 cannot be read through shop 8. That is an authorization and ownership assertion, not just a substitution check. Keep the positive route test with known fixture IDs, then add a separate cross-tenant negative scenario using identities that the test environment controls. If a gateway rewrites a base path, assert a stable response identifier rather than expecting an internal gateway route to match the public URL text.

Verify Step 5: Run mvn -q -Dtest=ParameterTest#pathAndQueryParametersCanCoexist test. Expect the route assertion to show both substituted segments and the decoded q value.

Step 6: Test Repeated Values and URL Encoding

Some APIs accept repeated query keys such as tag=api&tag=java. Others expect comma-separated values or a single JSON body field. Check the provider contract before choosing a representation. For this local API, pass the same key twice and assert the ordered decoded list.

@Test
void repeatedTagsAndSpecialCharactersSurviveEncoding() {
  String rawQuery = given()
      .baseUri(api.baseUri())
      .queryParam("tag", "api")
      .queryParam("tag", "java")
      .queryParam("q", "Java & API")
  .when()
      .get("/books")
  .then()
      .statusCode(200)
      .body("tags", equalTo(java.util.List.of("api", "java")))
      .body("q", equalTo("Java & API"))
      .extract()
      .path("rawQuery");

  assertTrue(rawQuery.contains("%26"),
      "The ampersand inside q must be URL encoded");
}

The decoded q must remain one value containing an ampersand. If you manually concatenate "/books?q=Java & API", the ampersand can be interpreted as a separator between query entries. REST Assured's URL encoding protects this boundary. The test checks %26 in the raw query but does not overconstrain whether a space is represented by a plus sign or %20. Both can decode to a space in common query parsing.

Do not pre-encode "Java & API" as "Java%20%26%20API" before passing it to queryParam. REST Assured may encode the percent signs again, producing a double-encoded request. Keep the application value unencoded and let the request library perform transport encoding. Only disable URL encoding for a fully controlled, already encoded URL after testing that specific case.

Non-ASCII search text deserves its own test when your API supports it. A word such as café becomes UTF-8 bytes before percent encoding, while the service should receive the original characters after decoding. Assert the semantic response, not a platform-dependent log rendering. Also test a literal plus sign if your search language uses one: query parsers often interpret an unescaped plus as a space. Sending the original value through queryParam lets the client encode that distinction for you.

Verify Step 6: Run mvn -q -Dtest=ParameterTest#repeatedTagsAndSpecialCharactersSurviveEncoding test. It should pass with two tags and the intact search phrase. If your actual provider treats repeated keys as one value, its contract differs from this fixture and the assertion should reflect that documented rule.

Step 7: Assert Invalid Input and Missing Resources

Parameter tests need negative cases because a well-formed request can still be invalid for the API. Here the fixture rejects page=0 before route handling and returns 404 for an unknown book. These tests check distinct status codes and error names; a broad any 4xx assertion would hide a changed validation rule.

@Test
void zeroPageIsRejected() {
  given()
      .baseUri(api.baseUri())
      .queryParam("page", 0)
  .when()
      .get("/books")
  .then()
      .statusCode(400)
      .body("error", equalTo("INVALID_PAGE"));
}

@Test
void unknownBookIsNotFound() {
  given()
      .baseUri(api.baseUri())
      .pathParam("bookId", "missing")
  .when()
      .get("/books/{bookId}")
  .then()
      .statusCode(404)
      .body("error", equalTo("BOOK_NOT_FOUND"));
}

The first test asks for an existing collection using an invalid paging value. The second uses a syntactically valid identifier for a resource the fixture does not contain. In a production service, a malformed ID might return 400 while a valid but absent ID returns 404. Decide that distinction from the service contract. The API error handling and negative testing guide covers deeper error matrix design.

Build a negative matrix from the parameter schema: zero, negative, nonnumeric, and excessive page values may have different outcomes. Do not assert a particular limit unless the API publishes one. For path IDs, distinguish an unknown well-formed value from an invalid format and from an ID owned by another caller. Record the expected status, error code, and safe response fields for each case. This matrix catches accidental fallback to the first page and accidental exposure of a different tenant's resource.

Verify Step 7: Run mvn -q -Dtest=ParameterTest#zeroPageIsRejected+unknownBookIsNotFound test. Surefire should execute both named methods and exit successfully. You can also run mvn -q test to check all methods accumulated so far.

Step 8: Reuse a Request Specification and Test a Page Boundary

A base URI and common Accept header can live in a request specification. Build a fresh specification for each test or keep a stable, immutable suite-level base; avoid mutating shared request state while tests run in parallel. This method creates one local spec, adds a page value through the request, and confirms the value that reached the server.

@ParameterizedTest
@CsvSource({"1,10", "2,25", "99,1"})
void paginationValuesReachTheServer(int page, int size) {
  RequestSpecification base = new RequestSpecBuilder()
      .setBaseUri(api.baseUri())
      .setAccept("application/json")
      .build();

  given()
      .spec(base)
      .queryParam("page", page)
      .queryParam("size", size)
  .when()
      .get("/books")
  .then()
      .statusCode(200)
      .body("page", equalTo(Integer.toString(page)))
      .body("size", equalTo(Integer.toString(size)));
}

This is a transport check, not a pagination behavior check. The fixture has no actual book pages, so it cannot prove that page two contains different items or that the final page has no next link. Against a real service, add assertions for item count, stable ordering, boundaries, total or cursor semantics, and duplicate records across pages. For the page-content assertions this fixture cannot make, follow the API pagination testing guide.

For a real cursor API, avoid converting cursors to page numbers in your tests. Follow the returned next cursor until the contract says the collection is exhausted, keeping a set of seen IDs to detect duplicates. For offset pagination, hold filters and sort keys constant across requests, otherwise records can appear to move between pages. If the data set changes while the test runs, use a stable fixture or snapshot mechanism; a count mismatch may reflect concurrent writes instead of a paging defect.

Verify Step 8: Run mvn -q -Dtest=ParameterTest#paginationValuesReachTheServer test. Surefire should run three invocations. Then run mvn -q test for the complete tutorial suite. The final command verifies all methods together and catches accidental fixture or import edits.

REST Assured Query and Path Parameters: Choosing the Right API

Need REST Assured call Address effect Example assertion
Select one resource pathParam("bookId", "b-42") Fills {bookId} in route Returned ID or route matches
Filter a collection queryParam("q", "java") Adds q=java after ? Results satisfy filter
Select a page queryParam("page", 2) Adds paging control Items and next link are correct
Repeat a key Call queryParam("tag", ...) twice Sends two tag entries Both values are honored
Send HTML form data formParam("name", "Ada") Sends a form field Server reads form body

Choose by HTTP contract, not by which method makes a test pass. A path segment usually identifies a resource or hierarchy. A query parameter usually selects a view, filter, sort order, or page. Some providers put API versions, tenants, or search terms in different locations; follow their published route definition. REST Assured provides both pathParams and queryParams overloads for maps when a test has dynamic sets, but named calls are clearer for a small fixed request.

Troubleshooting

Problem: REST Assured says a path placeholder has no value -> Make the names match exactly. {bookId} requires pathParam("bookId", value); capitalization and spelling matter. Check that no placeholder was left in a shared base path.

Problem: The server sees a form field instead of a query value -> Replace a generic param call with queryParam. This is especially important for POST requests, where method-specific generic parameter behavior can differ from a GET request.

Problem: An ampersand splits one search value into two parameters -> Pass the unencoded value through queryParam. Avoid hand-built strings with ? and & for dynamic text. Assert the decoded server value and inspect a safe raw query during diagnosis.

Problem: The provider rejects repeated tag keys -> Read its expected array encoding. It might require comma-separated text, indexed keys, or a JSON body. Change the request representation to that contract and add an assertion that both intended values affected the result.

Problem: Tests pass alone but fail together -> Check shared REST Assured static fields and mutable specifications. This tutorial uses a per-class local server and a per-test specification; production suites should isolate base URLs, credentials, and filters between tests.

Problem: Maven reports that no tests ran -> Confirm the file is src/test/java/example/ParameterTest.java, its class and method names match the command, and Surefire is configured as shown. Run mvn test without -q to inspect discovery messages.

Interview Questions and Answers

Q: What is the difference between pathParam and queryParam?

A path parameter replaces a named segment in a route template, commonly identifying a resource. A query parameter adds a key and value after the question mark, commonly controlling filtering or pagination. I assert both the selected resource and the effect of the query, because a 200 status alone does not prove either one.

Q: Why use named path parameters instead of string concatenation?

Named placeholders show which value fills each segment and let REST Assured perform URL encoding. They make routes with multiple IDs easier to review and reuse. Concatenation can misplace a value or mishandle a reserved character.

Q: How do you send repeated query values?

For an API that accepts repeated keys, I call queryParam for the same name more than once or pass its supported multi-value form. Then I assert the provider uses both values. I first check whether the contract expects repeated keys, because comma-separated arrays are a different wire format.

Q: What should a pagination parameter test assert?

It should verify valid boundary values reach the endpoint and that returned records, order, page metadata, and next-link behavior follow the contract. I test invalid page and size values separately. An echo fixture can verify request construction but cannot prove server pagination logic.

Q: How do you avoid double encoding?

I pass raw application values to pathParam and queryParam, then let REST Assured encode them. If a system requires an already encoded URL, I treat it as a specialized case, disable encoding only for that request, and inspect the actual transmitted address.

Q: Can a POST request have query parameters?

Yes. The HTTP method does not forbid query parameters. I use queryParam for URL controls and body or formParam for the request body according to the endpoint contract, avoiding the ambiguity of a generic param call.

Common Mistakes

  • Putting ?page=2 in a route template while also calling queryParam("page", 2).
  • Using a resource ID as a search filter and then failing to assert the specific resource selected.
  • Assuming two repeated keys are equivalent to one comma-separated value.
  • Comparing entire raw URLs when the order of independent query keys is irrelevant.
  • Pre-encoding spaces or ampersands before passing values to REST Assured.
  • Asserting only status 200 and missing a wrong shop or book path.
  • Treating a 400 validation failure and a 404 absence response as interchangeable.
  • Reusing a mutable request specification across parallel tests without isolation.

Conclusion

REST Assured Query and Path Parameters work best when each value has one clear place in the HTTP request. Name path placeholders for resource identity, add query controls through the request builder, and assert the result that each value should cause. The local suite verifies the wire shape; your service tests should also verify filtering, sorting, pagination, and authorization behavior.

Where To Go Next

Replace DemoApi with your service's test environment base URL and keep the assertions that prove which resource and filter were used. Add response content checks: verify that a search term actually narrows results, that sorting is stable, and that pagination has no gaps or duplicates. For token-protected endpoints, use the REST Assured OAuth2 authentication tutorial and keep credentials out of query strings.

When your suite grows, extract stable configuration into a request specification, then add schema checks for response shape with REST Assured JSON schema validation. Keep the small local fixture as a fast request-construction check if it guards a tricky URL encoding rule. The final goal is a test that proves both the address sent and the provider behavior returned.

Interview Questions and Answers

How would you decide whether an API value belongs in a path or query parameter?

I follow the route contract. A path segment usually identifies a resource or hierarchy, while a query value usually selects a filter, ordering, or page. I then assert the returned resource or collection behavior, not just the request status.

What failure can occur when concatenating dynamic query strings manually?

Reserved characters can change the structure of the query. An ampersand inside a search phrase may be read as a separator, and a pre-encoded value may be encoded again. I pass raw values through queryParam and verify the decoded server input.

How do you test two IDs in one REST Assured path?

I use distinct named placeholders, such as /shops/{shopId}/books/{bookId}, and call pathParam for each name. I assert the resolved route or returned identifiers to catch swaps between values. Positional substitution is possible but less readable for complex routes.

How do you test repeated query parameters?

First I confirm the provider accepts repeated keys. I send both values with repeated queryParam calls and assert both affect the response. I do not assume this is equivalent to one comma-separated string.

Why is a 200 status insufficient for parameter tests?

A service may return 200 for the wrong resource, an ignored filter, or a default page. I assert resource identifiers, filtered contents, ordering, and pagination metadata as appropriate. An echo fixture separately verifies request construction.

How do you distinguish invalid paging input from a missing resource?

I use separate scenarios with precise expected statuses and error codes. A page outside the accepted domain is a validation issue, while a well-formed ID with no matching record is a lookup issue. The provider contract determines the exact response.

How would you make a parameterized pagination test useful?

I include lower and upper accepted boundaries and a case beyond the limit, with separate expected outcomes. For real pagination I also compare item sets across pages and check cursor or next-link behavior. Simply echoing page values proves only transport.

Frequently Asked Questions

What is the difference between pathParam and queryParam in REST Assured?

pathParam fills a named placeholder inside the request path, such as /books/{bookId}. queryParam adds a key and value to the query string, such as ?page=2. Choose the one specified by the API contract.

How do I add multiple query parameters in REST Assured?

Chain queryParam calls on the request specification, one for each key, before the HTTP method call. Use queryParams for a map when the keys are generated dynamically. Assert the server received each value.

Can I use path and query parameters in the same REST Assured request?

Yes. Add pathParam values for route placeholders and queryParam values for URL controls on the same request. The tutorial's shop-book test demonstrates both and checks the route and filter.

Does REST Assured URL encode query parameter values?

REST Assured encodes parameter values by default. Pass the original text, including spaces and ampersands, rather than pre-encoding it. Verify the decoded value on the server and inspect the raw query only when diagnosing transport details.

How do I send repeated query keys with REST Assured?

Call queryParam with the same name for each intended value, or use its multi-value overload when the provider accepts repeated keys. Check the provider contract because some APIs require a comma-separated value or a body array instead.

Should I use param instead of queryParam for a POST request?

Use queryParam when the value belongs in the URL. The generic param method can represent form parameters for some HTTP methods, so it is less explicit. Use formParam only when the endpoint defines a form body.

How should I test an invalid page number?

Send the invalid value through queryParam and assert the exact documented status and error contract. Keep that test separate from a valid but absent resource so a 400 validation result cannot be confused with a 404 lookup result.

Related Guides