Q29 of 40 · Karate

Explain how Karate Gatling integrates performance testing with the same feature files.

KarateSeniorkarategatlingperformance-testingload-testingreuse

Short answer

Short answer: Add the karate-gatling dependency and create a Gatling simulation that references existing .feature files via KarateProtocol. The same Karate scenarios that run as functional tests now drive load. Set throughput, user counts, and assertion thresholds in the Gatling simulation class — no duplication between functional and performance test scripts.

Detail

Dependency:

<dependency>
    <groupId>com.intuit.karate</groupId>
    <artifactId>karate-gatling</artifactId>
    <version>1.4.1</version>
    <scope>test</scope>
</dependency>

Simulation class:

class CreateUserSimulation extends Simulation {
    val protocol = karateProtocol(
        "/users" -> Nil,
        "/users/{id}" -> Nil
    )

    val create = scenario("Create User")
        .exec(karateFeature("classpath:features/users/create-user.feature"))

    setUp(
        create.inject(
            rampUsers(50).during(30.seconds),
            constantUsersPerSec(10).during(60.seconds)
        )
    ).protocols(protocol)
     .assertions(
         global.responseTime.percentile(95).lte(500),
         global.successfulRequests.percent.gte(99.0)
     )
}

Key points:

  • karateFeature runs the .feature file for each virtual user — each user executes the full scenario
  • karateProtocol declares URL patterns so Gatling can aggregate stats per path
  • Assertions in setUp fail the build if p95 > 500ms or error rate > 1%
  • The Karate feature runs against a real API — Gatling provides the concurrency and reporting

What changes from functional tests: thread count (Gatling injects many virtual users), no JUnit assertions (Gatling assertions in the simulation), and response time percentiles replace correctness assertions as the primary metric.

// EXAMPLE

CheckoutPerfSimulation.scala

import com.intuit.karate.gatling.KarateProtocol
import com.intuit.karate.gatling.PreDef._
import io.gatling.core.Predef._
import scala.concurrent.duration._

class CheckoutPerfSimulation extends Simulation {

  val protocol = karateProtocol(
    "/cart"         -> Nil,
    "/checkout"     -> Nil,
    "/orders/{id}"  -> Nil
  )

  // Feature file already used for functional testing — reused here
  val checkoutScenario = scenario("Full Checkout Flow")
    .exec(karateFeature("classpath:features/orders/checkout.feature"))

  setUp(
    checkoutScenario.inject(
      atOnceUsers(1),                              // warm up
      rampUsers(20).during(10.seconds),            // ramp to 20 concurrent
      constantUsersPerSec(5).during(60.seconds)    // sustain 5 rps for 1 minute
    )
  ).protocols(protocol)
   .assertions(
     global.responseTime.percentile(95).lte(800),   // p95 < 800ms
     global.failedRequests.percent.lte(1.0)         // < 1% errors
   )
}

// WHAT INTERVIEWERS LOOK FOR

Understanding that the same .feature file drives both functional and load tests (no duplication), how karateProtocol() aggregates stats per URL pattern, and setting meaningful assertion thresholds in the simulation. The ramp-then-sustain injection pattern is a Gatling best practice.

// COMMON PITFALL

Running the Karate Gatling simulation during the regular JUnit test suite — it will inject 50 concurrent users against the test environment, causing other tests to fail from load-induced latency. Gatling simulations run separately (different Maven phase or profile).