skip to content

Once-Per-Run Caching

An expensive sign-in re-runs with every scenario and every example row unless it is cached, and Karate offers two caches of different widths. Interviewers ask which one you reach for.

on this pageshow

explore

questions

5

In a Karate feature file, a `Background` runs before every scenario — so how does `* def token = callonce read('auth-token.feature')` avoid re-running that feature for each one?

level: juniorimportance: must knowfreq 70%

answer

  1. One keyword differs by one letter
  2. The Background is not run once
  3. Result is remembered, callee is not re-run
  4. Cache belongs to the running feature

basics

~20 s

callonce runs the called feature only the first time and caches what it returned, so every later scenario in that feature file reuses it. Plain call re-executes the login for every scenario and every Examples row.

solid answer

~40 s

`call` and `callonce` take the same expression and behave identically apart from one thing: `callonce` remembers the result. The first scenario that reaches the step executes `login.feature`; every later scenario in the **same feature file** gets a copy of that cached result without a second HTTP round trip. That matters because Karate re-runs the whole `Background` before every `Scenario` and before every `Examples` row, so a `call` there is a sign-in per row, not per file. The cache is held on the running feature, so a second feature file that runs the same `callonce` line executes `login.feature` again. It is also keyed on the call expression itself, and it is thread-safe: parallel scenarios of that feature contend on a lock and only one of them actually runs the callee.

code

gherkin · 22 lines
gherkin
Feature: cats

Background:
  * url baseUrl
  # runs once for this whole file, not once per scenario
  * def auth = callonce read('login.feature')
  * header Authorization = auth.token

Scenario: list cats
  Given path 'cats'
  When method get
  Then status 200

Scenario Outline: fetch <id>
  Given path 'cats', <id>
  When method get
  Then status 200

  Examples:
    | id |
    | 1  |
    | 2  |

go deeper

for a junior

Recall that callonce is call plus a cache, and that the Background it usually sits in re-runs for every scenario and every Examples row.

for a middle

Be able to say where the cache lives (the running feature), what keys it (the call expression), and that a failed call is not cached.

for a senior

Judge which Background work is worth caching: expensive and idempotent yes, anything a scenario must see fresh no. Watch for functions smuggled through the result.

for a principal

Weigh per-file caching against a suite-wide one: callonce keeps each feature independently runnable, at the cost of one sign-in per file.

## The one difference `call` and `callonce` are two step keywords that accept exactly the same expression — a feature file read with `read(...)`, a JavaScript function, a variable holding either — and hand it the same optional argument. Everything about how the callee runs, what it can see and what it hands back is shared. The single difference is that `callonce` consults a cache first, and on a miss stores what the call produced. | | `call` | `callonce` | |---|---|---| | Executes the callee | every time the step runs | first time only | | Later runs of the step | full execution again | copy of the cached result | | Cache lifetime | none | the running feature | | Cache key | none | the call expression as written | ## Why the `Background` is where this bites Karate re-evaluates the `Background` before **every** `Scenario`, and before **every row** of an `Examples` table. That is the reason a cache exists at all: a feature file with three scenarios and a five-row outline runs its `Background` eight times, so a `call read('login.feature')` there is eight sign-ins. Swapping in `callonce` collapses those eight to one while leaving the rest of the `Background` — the `url`, the headers, the seed data — running per scenario as it should. The idiom that follows is the one you will see in real suites: - put cheap, scenario-local setup in the `Background` with plain steps; - put the expensive, idempotent part behind `callonce`; - keep anything that must be fresh per scenario **out** of the cached callee. ## What comes back on a cache hit On a hit the scenario is handed a copy of the cached result, not the cached object itself, so ordinary use does not hand the next scenario a mutated value. What is cached is *data* — the variables the callee produced. Karate's own documentation is blunt that a `callonce` result should ideally be pure JSON: the mechanism exists to cache **data, not behaviour**. A JavaScript function returned from a cached call still resolves its variables against the scope it was created in, which is the callee's context at the moment of the one real execution, not the scope of whichever scenario later receives it. ## Concurrency and failure Two details are worth knowing because they are easy to guess wrong: 1. **Parallel scenarios are handled.** The cache is guarded by a lock with a double-check, so when several scenarios of the same feature start at once exactly one executes the callee and the rest wait and take the cached result. You do not need to serialise anything yourself. 2. **A failed call is not cached.** If the callee throws, nothing is stored, and the next scenario in the feature tries again. This is the opposite of `karate.callSingle()`, which deliberately caches the exception so every later caller fails immediately with the same error. ## Where the boundary of the cache is The cache belongs to the feature that is running, not to the file being called. Concretely: - All scenarios and all `Examples` rows of one top-level feature share one cache, so the callee runs once for that file. - A second feature file containing the identical `callonce` line has its own cache and runs the callee again. Ten feature files means ten sign-ins. - A feature that is itself *called* gets a fresh cache on each invocation, so a `callonce` inside a called feature does not de-duplicate across the calls that invoke it. That last boundary is the practical reason `callonce` alone does not solve a suite-wide sign-in. When you want the login to happen once for the whole run regardless of how many feature files there are, the tool is `karate.callSingle()` from `karate-config.js`, whose cache is held for the suite instead of the feature. `callonce` is the right answer when the expensive thing is genuinely per-file — seeding records this feature will assert on, fetching a reference list this feature needs — and when you would rather each file be independently runnable. ## Recognising it in review A `call` in a `Background` is not automatically wrong; it is wrong when the callee is expensive or non-idempotent. Reading a small JSON fixture per scenario costs nothing. Creating three kittens per scenario, or exchanging credentials for a token per scenario, is the shape that wants `callonce` — and the symptom in a failing suite is usually the callee's own side effects piling up, not the wall-clock time.

  • In Karate, does `callonce` de-duplicate the call across two different feature files?
    No. The cache is held by the feature that is executing, so each top-level feature file has its own. Two files carrying the same `callonce read('login.feature')` line each execute `login.feature` once, giving two sign-ins. To collapse a login to one per run across every file, call it with `karate.callSingle()` from `karate-config.js`, whose cache is suite-wide.
  • In Karate, what happens on the next scenario if the feature behind a `callonce` fails?
    Nothing is written to the cache when the callee throws, so the failure propagates to the current scenario and the next scenario re-executes the call. That is worth contrasting with `karate.callSingle()`, which caches the exception itself and re-throws it to every later caller so the expensive failing step is attempted only once.
  • In Karate, is `callonce` safe when the feature's scenarios run in parallel?
    Yes. The lookup is guarded by a lock with a re-check after acquiring it, so when several scenarios of the feature hit the step simultaneously exactly one executes the callee and the others block briefly and then take the cached result. Karate logs the wait when a thread had to queue.

Like memoizing a function: same call, same arguments, and after the first evaluation the answer is handed back from a note rather than recomputed.

saying these in an interview costs you the question

  • Says callonce and call differ in what the callee can see
  • Thinks the Background already runs only once per feature
  • Believes callonce is global across the whole run
  • Expects a failed callonce to be cached and skipped afterwards
  • Returns JS functions or Java objects from a callonce result
  • Thinks Examples rows skip the Background
open as a page

In Karate, how do `callonce` and `karate.callSingle()` differ in how wide their cache is and in what each one uses as the cache key?

level: middleimportance: must knowfreq 62%

basics

~20 s

callonce caches per feature and keys on the call expression exactly as written. karate.callSingle() caches per suite, so once for the whole run across every feature, and keys on the path string you pass it.

open as a page

In a Karate feature file, what does `karate.setupOnce()` run, and how is it different from a `callonce` of another feature?

level: middleimportance: should knowfreq 30%

basics

~20 s

karate.setupOnce() runs the Scenario tagged @setup in the same feature file and caches its variables for that feature. Unlike callonce, the target is a scenario in this file rather than another feature, and it is skipped by any ordinary run.

open as a page

In Karate, what does `configure callSingleCache = { minutes: 15 }` change about `karate.callSingle()`, and what is the default?

level: seniorimportance: should knowfreq 34%

basics

~20 s

It spills the callSingle result to a file under the build directory and reuses it for fifteen minutes, so repeated local runs skip the sign-in entirely. The default is minutes 0, meaning memory only and one execution per run.

open as a page

Two scenarios in one Karate feature each need a different login, and both go through `* def token = callonce read('login.feature') creds` where `creds` is set per scenario. Both end up with the first scenario's token — why, and how do you fix it?

level: seniorimportance: should knowfreq 40%

basics

~20 s

The callonce cache is keyed on the expression text, not on the argument's value. Both steps carry identical text, so they share one entry and the second scenario gets the first one's token. Fix it by making the expressions differ.

open as a page