TestFly 1.1.0 adds built-in load and performance testing, powered by Gatling and lightweight virtual threads. You can write, parameterize and run high-concurrency load scenarios inside ordinary TestNG or JUnit 5 test classes, without switching to an external tool.
Why keep functional and load tests together
Performance testing often comes last in the development cycle, or it lives in a separate toolchain such as the JMeter GUI or standalone scripts. That split causes a few problems:
- Test data and authentication setup get duplicated between the functional and performance repos.
- Performance tests run rarely, often right before a production release.
- CI/CD quality gates are hard to configure when the tests run on different runners.
In TestFly you declare load simulations with the same test classes, base configuration (testfly.yml) and assertions you already use for WebUI and API automation.
How load testing works in TestFly
There are two ways to write a load test.
1. Declarative mode with @LoadTest
Add @LoadTest to a test method to set the number of concurrent virtual users and the ramp-up phase:
package io.testfly.examples.loadtest;
import io.testfly.loadtest.LoadTest;
import io.testfly.test.BaseTest;
import org.testng.annotations.Test;
public class SimpleLoadTest extends BaseTest {
@Test
@LoadTest(users = 100, rampUpSeconds = 15, durationSeconds = 60)
public void testCheckoutUnderSurgeTraffic() {
apiClient().post("/api/checkout")
.body("{\"cartId\": 1024}")
.send()
.assertStatus(200);
}
}
2. Fluent programmatic DSL with load()
For multi-step user journeys and dynamic feeders, use the fluent LoadScenario DSL:
package io.testfly.examples.loadtest;
import io.testfly.loadtest.LoadScenario;
import io.testfly.loadtest.LoadTestFeeder;
import io.testfly.test.BaseTest;
import org.testng.annotations.Test;
public class AdvancedLoadJourneyTest extends BaseTest {
@Test
public void simulateUserBrowsingJourney() {
LoadTestFeeder users = LoadTestFeeder.csv("testdata/users.csv");
LoadScenario.scenario("Search and Add to Cart")
.feed(users)
.step("Browse Catalog", client -> client.get("/products?q=shoes").send().assertStatus(200))
.pause(1000)
.step("Add to Cart", client -> client.post("/cart/items").body("{\"id\": 42}").send().assertStatus(201))
.users(500)
.rampUp(20)
.run();
}
}
Checking performance SLOs in code
Metrics only help if something checks them automatically. TestFly records the timing of every request with millisecond precision and computes the standard percentiles (P50, P90, P95, P99):
| Percentile Metric | Meaning | Typical SLO Target |
|---|---|---|
| P50 (Median) | 50% of requests completed faster than this time | < 150 ms |
| P90 | 90% of requests completed faster than this time | < 350 ms |
| P95 | 95% of requests completed faster than this time | < 500 ms |
| P99 | Tail latency affecting 1% of users | < 1200 ms |
Write your service-level objectives as assertions and the build fails on its own when performance regresses:
import static io.testfly.loadtest.LoadTestAssert.*;
assertP95Below(Duration.ofMillis(450));
assertErrorRateBelow(0.01); // Less than 1% failures
Reporting
TestFly produces an interactive HTML performance report with:
- live latency distribution curves and percentile breakdowns
- HTTP status code distributions (2xx, 4xx, 5xx)
- direct export to Allure Reports and ReportPortal, so load results show up in the same dashboards as the rest of your tests
FAQ
Does TestFly require JMeter or an external binary?
No. Simulations run in-process on Gatling's engine or on native Java virtual threads, so there is no external runtime binary to install.
Can I run load tests in CI/CD without overwhelming local machines?
Yes. Load tests run headless in CI (GitHub Actions, Bitbucket, Jenkins). For very large loads (100k+ RPS) you can schedule containerized runner pods on Kubernetes.
Can I use API authentication tokens in load tests?
Yes. TestFly's ApiClient passes Bearer tokens and session headers to the load test steps.
Originally published on hakangul.lovable.app.
Top comments (0)