In Karate's JUnit integration, what is the `@Karate.Test` annotation, where may it be placed, and what must the annotated method return?
answer
- It is sugar over a JUnit annotation
- Methods only, never classes
- The return type is the mechanism
- Each scenario becomes its own node
- Karate registers no test engine
basics
~10 sKarate's @Karate.Test is a method-level annotation meta-annotated with JUnit's @TestFactory. The method must return a Karate instance, an Iterable of dynamic nodes, so each feature and scenario becomes its own test.
solid answer
~40 s`@Karate.Test` is a nested annotation on Karate's JUnit `Karate` class, targeted at **methods** and retained at runtime, and it is meta-annotated with JUnit Jupiter's **`@TestFactory`**. That single fact explains the whole mechanism: the annotated method must return a `Karate` object built with `Karate.run("orders").relativeTo(getClass())`, and because `Karate` implements `Iterable<DynamicNode>`, JUnit's dynamic-test machinery walks it and reports each feature and scenario as its own node in the IDE and the report. Karate ships **no JUnit Platform TestEngine** and no suite annotation of its own — it rides entirely on `@TestFactory`, which is why a plain `@TestFactory` on a method returning the same object behaves identically. It is the development-time entry point; the `Runner.path(...).parallel(n)` chain is the one you point CI at.
code
java · 15 linesimport com.intuit.karate.junit5.Karate;
class OrdersRunner {
@Karate.Test
Karate testOrders() {
// orders.feature sits beside this class
return Karate.run("orders").relativeTo(getClass());
}
@Karate.Test
Karate testSmokeOnly() {
return Karate.run("orders").tags("@smoke").relativeTo(getClass());
}
}go deeper
Recall that it goes on a method, that the method returns a Karate instance, and that JUnit then shows one node per feature and scenario.
Explain the meta-annotation: it is @TestFactory underneath, and the returned object works only because the Karate class implements Iterable of DynamicNode.
Position the two entry points deliberately — annotated methods for developing beside the features, one parallel runner asserting on the aggregate count for CI.
Note how little Karate asks of the test platform: no engine, no suite annotation, one meta-annotation, which is what keeps the integration portable as JUnit itself moves.
## What the annotation actually is Strip away the convenience and `@Karate.Test` is three lines of Java: - `@Target(ElementType.METHOD)` — it goes on a **method**, never on a class. Putting it on a class does not compile. - `@Retention(RetentionPolicy.RUNTIME)` — visible to JUnit at discovery time. - `@TestFactory` — the meta-annotation that does all the work. Because `@TestFactory` is meta-annotated onto it, JUnit treats the annotated method exactly as if you had written `@TestFactory` yourself. `@Karate.Test` is sugar with a name that reads well next to Karate code; it grants no capability that `@TestFactory` does not already provide. ## Why the return type matters A `@TestFactory` method must return something JUnit can turn into dynamic tests — a `Stream`, `Collection`, `Iterator` or `Iterable` of `DynamicNode`. Karate's JUnit `Karate` class **implements `Iterable<DynamicNode>`**, so returning it satisfies that contract directly: ```java class OrdersRunner { @Karate.Test Karate testOrders() { return Karate.run("orders").relativeTo(getClass()); } } ``` `Karate.run("orders")` is a shortcut for a new instance with that path set, and `relativeTo(getClass())` anchors it to the annotated class's own package so the feature file can sit beside the Java file. Iterating the returned object is what starts execution: each feature becomes a container node and each scenario a test node, which is why the IDE shows a tree that expands into individual scenarios rather than one opaque green tick. ## The two entry points, and which is which | | `@Karate.Test` | `Runner.path(...).parallel(n)` | |---|---|---| | shape | annotated method returning `Karate` | plain Java inside an ordinary `@Test` | | reporting | one JUnit node per feature and scenario | one JUnit result for the whole suite | | failure surfacing | JUnit fails the individual node | you assert on the returned failure count | | typical use | running and debugging in an IDE | the CI entry point | They are not competitors. A project commonly has both: small annotated methods next to the feature packages for development, and one parallel runner class that CI invokes. The dividing line used to be sharper. **On Karate 1.x the JUnit entry point is deliberately single-threaded** — its `threads(int)` returns the underlying builder rather than the `Karate` object, which breaks the chain and stops you handing a thread count to something JUnit is going to iterate. **Karate 2.x removes that restriction**: its JUnit class exposes `threads(int)` returning itself, and streams results back as scenarios complete. If you are on 1.x and want parallelism, the parallel runner is the only route. ## What Karate does *not* ship This is where the interview usually goes next, and the answer is a list of absences: - **No JUnit Platform `TestEngine`.** Karate never registers an engine, so nothing discovers `.feature` files on its own. A Java method must name them. - **No suite annotation of its own.** There is no Karate equivalent of a `@Suite`-style class annotation that points at feature directories. - **No `@RunWith`-style runner** in the modern API. Everything goes through `@TestFactory` or through plain Java in a `@Test` body. - **No step-definition scanning**, so the annotation carries no glue or package argument — only a path. The consequence is that Karate's JUnit integration is unusually thin. It is one annotation, one class implementing `Iterable<DynamicNode>`, and JUnit's own dynamic-test feature doing the rest. ## Practical notes - **The method's visibility can be package-private**, like any Jupiter test method; it does not need to be `public`. - **Name the method after what it runs.** JUnit uses the method name as the parent node's display name, so `testOrders` reads better than `test1` when a scenario fails three levels down. - **`relativeTo(getClass())` is what keeps the path short.** Without it you are back to `classpath:` paths, which is fine but less convenient for a file sitting beside the class. - **One annotated method per feature area** keeps the IDE tree readable and lets you run a slice by clicking its gutter icon. - **Do not point CI at these.** They are per-package and single-purpose; a suite-wide run wants the parallel runner and its aggregate failure count. ## Reading one in review When you meet an annotated method in a codebase, three checks tell you whether it does what its author thought: 1. **Is the annotation on a method?** It cannot compile on a class, but a reviewer scanning quickly can still misread a class-level `@Karate.Test` in a diff that never built. 2. **Is the return value the configured object?** Building a `Karate` instance, configuring it, and then returning a different one is an easy slip in a method with two chains, and it fails silently by running the wrong path. 3. **Does the path resolve from the anchor?** `relativeTo(getClass())` anchors to the annotated class's package. Move the Java file without moving the feature file and the method finds nothing — which, as with any empty selection, is a fast green rather than an error.
- Could you replace `@Karate.Test` with JUnit's own `@TestFactory`?Yes, and it behaves identically. `@Karate.Test` is meta-annotated with `@TestFactory`, so JUnit's discovery treats them the same. The Karate-named form exists only for readability; either way the method must return something JUnit can turn into dynamic tests, which the `Karate` class satisfies by implementing `Iterable<DynamicNode>`.
- Why does an IDE show each Karate scenario as its own test node?Because the method is a `@TestFactory` and the returned `Karate` object is an `Iterable<DynamicNode>`. JUnit iterates it and creates a container node per feature with a test node per scenario, so failures land on the individual scenario rather than collapsing into one result for the whole method.
- When would you use this instead of `Runner.path(...).parallel(n)`?For development and debugging, where per-scenario nodes and a clickable gutter icon beside the feature file matter. The parallel runner is the CI entry point: it reports one aggregate result and lets you assert on the failure count, which is what a build needs from a suite of hundreds of scenarios.
saying these in an interview costs you the question
- Placing the annotation on a class rather than a method
- Saying Karate registers its own JUnit Platform TestEngine
- Expecting a glue or package argument on the annotation
- Returning void or a boolean from the annotated method
- Claiming it is the entry point CI should invoke