Q10 of 40 · Karate

What is a Karate feature reference (call read('classpath:...')) and when is it useful?

KarateMidkaratecall-readfeature-compositionhelpersreuse

Short answer

Short answer: call read('classpath:path/to/feature.feature') executes another feature file inline and returns its exported variables as a map. Pass arguments as a JSON object after the path. Use it to share authentication flows, reusable data-setup sequences, or parameterised sub-scenarios across feature files.

Detail

call read is Karate's mechanism for feature-file composition:

# Call with no args
* def authResult = call read('classpath:helpers/login.feature')
* def token = authResult.accessToken

# Call with arguments (the called feature receives them as variables)
* def user = call read('classpath:helpers/create-user.feature')
  { name: 'Alice', email: 'alice@example.com' }
* def userId = user.id

Inside the called feature (create-user.feature):

Feature: Create user helper
  Scenario:
    Given url baseUrl
    And   path '/users'
    And   request { name: '#(name)', email: '#(email)' }
    When  method POST
    Then  status 201
    * def id = response.id   # exported — available to caller

The #(name) syntax embeds a Karate variable into a JSON string — called "embedded expressions".

When to use call read:

  1. Authentication (login.feature) called from Background in every feature
  2. Test data setup (create-user.feature, create-order.feature) called in @BeforeEach equivalent
  3. Shared sub-scenarios called from a Scenario Outline

callonce — like call but caches the result for the duration of the suite. Ideal for expensive setup (token fetch, seeding reference data) that should run once.

// EXAMPLE

order-flow.feature

Feature: Order flow test

  Background:
    * url karate.properties['api.base.url']
    # callonce — fetches token once for the entire suite (cached)
    * def auth = callonce read('classpath:helpers/login.feature')
    * header Authorization = 'Bearer ' + auth.token

  Scenario: Create and fulfil an order
    # Set up a user (call — runs fresh each scenario)
    * def newUser = call read('classpath:helpers/create-user.feature')
      { name: 'Alice', email: 'a-' + karate.random(1000) + '@test.com' }

    # Create order for that user
    * def order = call read('classpath:helpers/create-order.feature')
      { userId: newUser.id, sku: 'WIDGET-1', qty: 2 }

    # Fulfil it
    Given path '/orders/' + order.id + '/fulfil'
    When  method POST
    Then  status 200
    And   match response.status == 'FULFILLED'

// WHAT INTERVIEWERS LOOK FOR

Understanding call vs callonce (fresh vs cached), how arguments are passed and how variables are exported, and the practical patterns (auth helper, data setup helper). The embedded expression #(variable) syntax in request bodies is a real detail.

// COMMON PITFALL

Using call instead of callonce for an expensive login feature — every scenario re-authenticates, adding latency. callonce is the right choice for suite-wide shared resources.