For a few years I've used Cucumber for higher level testing, and only more recently started using Karate. While Cucumber is a great tool, I think Karate really shines in reducing the boilerplate that comes along with step definitions and making it easy to write meaningful tests quickly, especially when it comes to API testing.
For simple applications, writing your feature files in plain JavaScript will suffice. As your application and tests grow, reusing some Java code can become valuable though. Spring Boot APIs can benefit a lot from Karate testing, but what about leveraging the power of Spring Boot directly in your Karate tests?
Some example use cases
- While Karate supports configuration via karate-config.js files, some may like configuring via Spring YAML/properties instead. This can be helpful to reconfigure things outside of rebuilding code as well.
- To sync certain Spring Boot configuration properties between application and tests.
- To verify database state between API calls. JPA repository/entity beans could be used in the Karate tests.
- Some Spring beans may simply be useful to utilize in tests.
How to integrate Spring into Karate
Full sample project: https://github.com/trey-pero/karate-spring
Karate can be executed via a simple JUnit test. To begin wiring Spring in, setup the JUnit test as a @SpringBootTest.
@RequiredArgsConstructor
@SpringBootTest(classes = Main.class)
public class KarateTest {
private final ApplicationContext applicationContext;
@Test
void test() {
ApplicationContextHolder.setApplicationContext(this.applicationContext);
// Since this one JUnit test runs all Karate tests,
// fail the test if any underlying Karate tests fail
assertEquals(0, Runner.path("classpath:org/tpero")
.parallel(Optional.ofNullable(System.getProperty("karate.threads"))
.map(Integer::parseInt)
.orElse(5)
).getFailCount());
}
}
In order to access the Spring context (which provides access to all beans and configuration), it needs to be stored somewhere that Karate can access it statically.
/**
* Provides Karate static access to the Spring application context.
*/
@UtilityClass
public class ApplicationContextHolder {
@Setter
@Getter
private ApplicationContext applicationContext;
}
From Karate config, the static holder can be accessed to wire the application context into Karate's global configuration map using the following sample:
/**
* Define common feature file configuration here.
* @returns Common configuration as a JSON object.
*/
function getConfig() {
// Global values
const appContext = Java.type("org.tpero.ApplicationContextHolder")
.getApplicationContext()
const environment = appContext.getEnvironment()
return {
appContext: appContext,
environment: environment,
baseUrl: `http://localhost:${environment.getProperty('app.server.port', '8080')}`
}
}
With the above setup code, beans and configuration can be accessed from Karate feature files, shown by this sample which tests a simple login API that returns a JWT token.
Feature: Login
Background:
* url baseUrl
* path '/login'
# Load the JWT service bean from Spring DI
* def jwtService = appContext.getBean('jwtService')
Scenario: Login with valid credentials
Given request { username: 'user', password: 'password' }
When method post
Then status 200
* print response
# Use the JWT service bean to decode the JWT from the response
* def decodedJwt = jwtService.decode(response)
* print decodedJwt
* def decodedBody = decodedJwt.getBody()
* print decodedBody
And match decodedBody['sub'] == 'user'
* def issuedAt = Number(decodedBody['iat'])
# Ensure the issuedAt is in the past
And assert issuedAt < Java.type('java.lang.System').currentTimeMillis()
* def configuredExpirationInMinutes = Number(environment.getProperty('jwt.expiration.ms')) / 1000
# Ensure the expiration is the configurable amount of minutes beyond the issuedAt
And match Number(decodedBody['exp']) == issuedAt + configuredExpirationInMinutes
This sample demonstrates how easy it is to integrate the power of Spring Boot into Karate to build more capable test suites.
Top comments (0)