skip to content

In REST Assured, how do given().baseUri() and given().basePath() decide where a request lands?

level: juniorimportance: must knowfreq 72%

answer

  1. three pieces joined into one URL
  2. base URI, then base path, then verb path
  3. one slash inserted, doubles collapsed
  4. per-request call overwrites the static copy
  5. nothing set means localhost

basics

~20 s

REST Assured builds the target URL by joining baseUri, then basePath, then the path given to the verb. Exactly one slash is inserted between the parts. Both methods override the matching static field for that single request only.

solid answer

~50 s

`given().baseUri(...)` and `given().basePath(...)` set the two pieces that sit in front of the path you hand to a verb. On an allotment water-rota API, `given().baseUri("https://rota.allotment-water.test").basePath("/api/v2").get("/plots/17/slots")` targets `https://rota.allotment-water.test/api/v2/plots/17/slots`. REST Assured concatenates the base URI (including any path the base URI itself carries), then the base path, then the verb's path, normalising the seams: a missing slash is inserted and a doubled one collapsed. Both calls are per-request — they overwrite the values the specification copied from the static `RestAssured.baseURI` and `RestAssured.basePath` at `given()`, and nothing outside that chain sees the change. There is no merge and no precedence arithmetic: the last write to the specification's field is what the verb reads. With neither set, the base URI is `RestAssured.DEFAULT_URI`, the literal `http://localhost`, and the base path is empty. Reach for the per-request form when one call must leave the suite's usual target — a warden-portal health probe, say.

code

java · 22 lines
java
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;

// -> GET https://rota.allotment-water.test/api/v2/plots/17/slots
given()
    .baseUri("https://rota.allotment-water.test")
    .basePath("/api/v2")
.when()
    .get("/plots/17/slots")
.then()
    .statusCode(200)
    .body("plotId", equalTo(17));

// A base URI carrying its own prefix; the seams are normalised
// -> GET https://rota.allotment-water.test/gateway/api/v2/taps/3/bookings
given()
    .baseUri("https://rota.allotment-water.test/gateway/")
    .basePath("/api/v2")
.when()
    .get("taps/3/bookings")
.then()
    .statusCode(200);

go deeper

for a junior

Be ready to write the chain from memory and say out loud which URL it produces. Knowing that base URI plus base path plus verb path are concatenated, with slashes normalised, is the whole expected answer here.

for a middle

Explain the mechanics: the statics are copied into the specification when the chain starts, and the per-request call overwrites that copy rather than merging with it. Mention the empty base path and the localhost default.

for a senior

Show the operational angle — which piece you would make configurable, why the version prefix belongs in the base path rather than in every verb call, and how you would prove where a failing test actually sent its request.

for a principal

Own the convention: one place decides the host for a run, the API's own prefix lives in the base path, and per-request overrides are reserved for genuine exceptions rather than used as a substitute for a shared target.

REST Assured never holds a finished URL for a request. It holds three separate pieces and joins them at the moment a verb is called, so the address a test actually reaches is always the product of an assembly rule rather than a stored string. Knowing that rule is the difference between a suite you can repoint at another environment in one line and one that quietly hits the wrong host. ## The three pieces - **Base URI** — the scheme and authority, optionally carrying a path prefix of its own; set it with `given().baseUri("https://rota.allotment-water.test")`. - **Base path** — a path fragment shared by a family of calls; set it with `given().basePath("/api/v2")`. - **The verb path** — the string handed to `get(...)`, `post(...)`, `delete(...)` or `request(...)`, for example `"/plots/17/slots"`. Written against an allotment water-rota API, `given().baseUri("https://rota.allotment-water.test").basePath("/api/v2").when().get("/plots/17/slots")` sends a GET to `https://rota.allotment-water.test/api/v2/plots/17/slots`. ## How the join works REST Assured concatenates the pieces left to right and normalises every seam, so you never have to count slashes: 1. Start with the base URI's scheme and authority. 2. Append the path the base URI itself carries, if any — `https://rota.allotment-water.test/gateway` contributes `/gateway`. 3. Append the base path. 4. Append the verb path. At each seam a single slash is inserted when neither side supplies one, and a doubled slash is collapsed back to one. That is why `baseUri("http://localhost/")` with `get("/plots")` and `baseUri("http://localhost")` with `get("plots")` resolve to the same address. It also means a base URI that already carries a prefix and a base path **stack** rather than compete: `/gateway` plus `/api/v2` plus `/plots` yields `/gateway/api/v2/plots`. ## Per-request call versus the static field Both names exist twice — as a field on `io.restassured.RestAssured` and as a method on `RequestSpecification`. The relationship is copy-then-overwrite: | | `RestAssured.baseURI` / `RestAssured.basePath` | `given().baseUri(...)` / `given().basePath(...)` | |---|---|---| | Scope | every request built afterwards | only the chain you called it on | | When it is read | copied into the specification at `given()` | written onto that specification directly | | Effect on other tests | changes all of them | none | | Typical use | the suite's normal target | one call that must go somewhere else | Because the statics are copied into the specification when the chain is created, a per-request call simply overwrites the copy. There is no merge and no "most specific wins" arithmetic: the last write to the specification's field is what the verb reads. So `given().baseUri("https://warden.allotment-water.test").get("/plots")` in one test leaves every other test pointed wherever the suite normally points. That also means the two forms are not in competition and never need to be reconciled. A suite normally sets the target once, in whatever hook runs before the first test, and then writes plain relative paths everywhere; the per-request methods are for the handful of calls that must go somewhere else. Reading a chain top to bottom therefore tells you the whole answer: if it names a base URI, that is the target, and if it does not, the target is whatever the run was configured with. ## What you get when you set nothing - The base URI defaults to `RestAssured.DEFAULT_URI`, the literal string `http://localhost`. - The base path defaults to `RestAssured.DEFAULT_PATH`, the empty string. - A bare `get("/plots")` in a fresh JVM therefore targets `http://localhost:8080/plots`; the `8080` comes from a separate port rule that fires for a `localhost` authority. - Passing `null` to either method throws a `NullPointerException` naming `"Base URI"` or `"Base Path"` — there is no argument that unsets them again. That default is shaped for a service booted on the same machine as the test run. The moment a suite targets anything else, one of the two has to be set explicitly. ## Putting each piece in the right place - Put the **scheme, host and port** in the base URI: that is the part that differs between a laptop and a deployed environment. - Put the **version or gateway prefix** in the base path. `/api/v2` is a property of the API rather than of the environment, so it belongs where a target switch will not disturb it. - Put the **resource path** in the verb, so a reader scanning the test sees `/plots/17/slots` at the call site instead of buried in setup. - Keep that verb path **relative**. A path beginning with a scheme discards both defaults outright, and that is the most common reason a repointed suite still reaches the old host. The payoff is an address assembled from parts you can each change independently, every one of them visible in the chain that sends the request.

  • If the base URI already ends in a path segment, does the base path replace it?
    No — they stack. REST Assured keeps the path carried by the base URI, appends the base path after it, then appends the verb path. A base URI of `https://rota.allotment-water.test/gateway` with a base path of `/api/v2` and a verb path of `/plots` resolves to `/gateway/api/v2/plots`.
  • What does REST Assured target if you never set a base URI or base path at all?
    `RestAssured.DEFAULT_URI`, the string `http://localhost`, with an empty base path from `RestAssured.DEFAULT_PATH`. A bare `get("/plots")` then goes to `http://localhost:8080/plots`, because the port rule appends 8080 when the authority is exactly `localhost`. Nothing fails or warns; the request simply goes to a local address.

Think of a postal address: the base URI is the town, the base path is the street, and the verb path is the house number. Change the town and every letter still finds the same street and number.

saying these in an interview costs you the question

  • Thinks basePath is prepended to the host rather than to the path
  • Believes a base URI may not carry a path prefix of its own
  • Assumes given().baseUri() also changes RestAssured.baseURI for later tests
  • Adds slashes by hand and expects a doubled slash to survive
  • Thinks an unset base URI makes the request fail rather than target localhost