Mastering Karate’s Call Feature: Reusable Test Logic in a Single Feature File
Use Karate's call and callonce to reuse feature logic, pass JSON data, and capture return values. Learn how to structure calls, avoid pitfalls, and verify results with the HTML report.
06 Mar 2026, 21:54 UTC

What You’ll Get
Karate’s call and callonce keywords let you treat one .feature file as a subroutine. This means you can keep common setup steps, authentication, or data‑driven loops in a single place and reuse them across an entire suite, while still capturing return values and keeping tests isolated.
How the Call Mechanism Works
In Karate, a call statement runs another feature file, passing a JSON object as the second argument. The called file receives that object as a variable called request (or the name you give it). When the called feature finishes, its last * status or * def is returned to the caller as a JSON object. This makes it possible to build a clear contract: the caller supplies input, the callee returns output.
Example: Reusable Login Logic
Create a common.feature that logs in and returns a token.
# common.feature
Feature: Common API helpers
Scenario: Login and return token
Given url baseUrl
And path '/login'
And request { username: '#(username)', password: '#(password)' }
When method post
Then status 200
And def token = response.token
* def result = { token: token }
* return result
Now call it from main.feature and capture the token.
# main.feature
Feature: User flow
Scenario: Perform action with authenticated user
* def loginData = { username: 'alice', password: 'secret' }
* def auth = call read('classpath:common.feature'), loginData
* def token = auth.token
Given url baseUrl
And header Authorization = 'Bearer ' + token
And path '/secure-endpoint'
When method get
Then status 200
Notice how the loginData object is passed in, and the returned auth object contains the token.
Using callonce for Global Setup
When you need a piece of logic to run only once per test suite—such as creating a test tenant or fetching a global config—use callonce. The first call executes the feature; subsequent calls return the cached result without re‑executing the steps.
# env.feature
Feature: Global environment
Scenario: Setup tenant
Given url baseUrl
And path '/setup-tenant'
When method post
Then status 201
And def tenantId = response.id
* def result = { tenantId: tenantId }
* return result
# any.feature
Scenario: Use tenant
* def env = callonce read('classpath:env.feature')
* def tenantId = env.tenantId
# ... use tenantId in subsequent steps
Running the suite twice will show that the Setup tenant steps execute only the first time, saving time and avoiding duplicate resources.
Common Pitfalls and How to Avoid Them
- Nested Calls Blow Up the Call Stack – Deeply nested
callchains can make debugging traces hard to read and increase memory usage. Keep nesting to one or two levels and usecalloncefor shared setup. - Variable Shadowing – If the called feature defines a variable with the same name as one in the caller, the caller’s value will be overwritten after the call. Use unique names or prefix variables (e.g.,
common_). - Over‑Reliance on
callonce– Hidden dependencies between tests can break isolation. Usecallonceonly for truly global, idempotent setup. - Forgetting the Return – The called feature must end with a
returnstatement. Without it, the caller receivesnulland any subsequent assertions fail. - Incorrect JSON Path – Passing a plain string or an object without the correct keys will cause runtime errors. Validate the JSON structure before calling.
Verifying Your Setup
- Create
common.featureandmain.featureas shown above. - Run the suite with Maven:
- Open the generated
target/karate-reportsHTML report. You should see a call hierarchy:main.feature→common.feature, and the returned token displayed in the console output. - To test
callonce, run the suite twice and confirm that theSetup tenantscenario appears only once in the report.
mvn test -Dtest=karate.Runner -Dkarate.options="-f src/test/java -c src/test/java"
Use karate.log() inside the called feature to print debug information if the call doesn’t behave as expected.
When to Use call vs. def Functions
If you only need to reuse JavaScript logic, consider defining a karate-config.js function. call is ideal when you need a full feature context: HTTP steps, assertions, and a clear separation of concerns.
Conclusion
By leveraging call and callonce, you can keep your Karate test suite DRY, maintainable, and fast. Remember to keep nesting shallow, avoid variable collisions, and verify the call hierarchy in the HTML report to catch subtle bugs early.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.