# In tests (/docs/java/testing)

The JUnit extension turns what Tracepanel finds into test failures, so an
N+1 query fails CI before it reaches production. It comes with the
`dev.tracepanel:tracepanel-junit` library and needs JUnit Jupiter 5.12 or
later, JUnit 6 included.

## Turn it on [#turn-it-on]

Add the library to the test dependencies. With Gradle:

```kotlin
// build.gradle.kts
dependencies {
    testImplementation("dev.tracepanel:tracepanel-junit:0.9.0")
}
```

With Maven:

```xml
<dependency>
  <groupId>dev.tracepanel</groupId>
  <artifactId>tracepanel-junit</artifactId>
  <version>0.9.0</version>
  <scope>test</scope>
</dependency>
```

Then put `@Tracepanel` on a test class, or turn the extension on for every
test class with JUnit's extension autodetection, which finds it in the jar:

```properties
# src/test/resources/junit-platform.properties
junit.jupiter.extensions.autodetection.enabled=true
```

`tracepanel.failOn` sets the kinds of issues that fail a test, in the same
file or as a system property:

```properties
tracepanel.failOn=n+1, duplicates, slow-sql
```

It takes the [issue](/docs/issues) names that `has:` takes in a search, such
as `slow-sql` or `error`. The default is `n+1` and `duplicates`.

The extension turns Tracepanel on in the test JVM, as development does, with
the widget off, and starts the project's agent when none is running. Spring
Boot's test slices, `@WebMvcTest`, `@JdbcTest`, `@DataJpaTest`,
`@DataJdbcTest` and `@JooqTest`, record their requests and queries as a
`@SpringBootTest` does. Outside Spring, wrap the `DataSource` your tests use
with `Jdbc.dataSource(dataSource, "default")`.

## What a test records [#what-a-test-records]

Each test runs as a `test` trace named after its class and method. A request
the test makes of the app, with `MockMvc`, `TestRestTemplate`,
`WebTestClient` or Ktor's `testApplication` client, is a trace of its own,
linked to the test's in the console. `@BeforeAll` and `@BeforeEach` run
outside the test's trace, so seeding the database doesn't count. A
parameterized test is a trace for each of its runs.

When the test passes, the agent looks at its traces. An issue of a kind in
`tracepanel.failOn` fails the test with the issue, its query, the line of
code that ran it and a link to the trace:

```text
AssertionFailedError: tracepanel found 1 issue in this test:

N+1: 4 similar queries · SELECT author
  SELECT name FROM author WHERE id = ?
  at src/main/java/shop/Books.java:20 in lambda$books$0
  in GET /books → http://127.0.0.1:52068/#/t/01M60NB74Q6T9AXZGMFJ7V8FB1
  Load the related rows with the parent rows (a join, or one batched query) ...

The test's trace: http://127.0.0.1:52068/#/t/01M60NB74J4Q5VDHCJD1Z4T1J7
Allowed here with @Tracepanel(allow = "n+1").
```

## Change the rules for a test [#change-the-rules-for-a-test]

`@Tracepanel` on a test method or class changes the rules for its tests. A
method's own rules come first, then those of its class and the classes
around it:

| Rule | What it does |
| --- | --- |
| `maxQueries` | Fails the test when it and its requests make more queries than this. |
| `failOn` | Replaces the kinds of issues that fail the test. |
| `allow` | Takes kinds of issues out of those that fail the test. |

A misspelled kind fails the test that names it, before it runs.

A test method can take its trace as a `TestTrace` parameter, or get it from
`testTrace()`: `queries()` counts the queries so far, its requests'
included, and `sql()` lists them, each with its statement and the line of
code that ran it. `issues()` lists what the agent found in the test's
requests so far, and `url()` is the test's trace in the console, or null
without an agent. `maxQueries(n, block)` fails when the block makes more
than `n` queries:

```java
import static dev.tracepanel.junit.TracepanelExtension.maxQueries;

@Tracepanel
@SpringBootTest
@AutoConfigureMockMvc
class FeedTest {
    @Autowired
    MockMvc mvc;

    @Test
    @Tracepanel(maxQueries = 10, allow = "n+1")
    void feed(TestTrace trace) throws Exception {
        mvc.perform(get("/feed")).andExpect(status().isOk());
        assertThat(trace.queries()).isLessThanOrEqualTo(10);
        maxQueries(2, () -> mvc.perform(get("/feed?page=2")));
    }
}
```

## Without an agent [#without-an-agent]

With `TRACEPANEL_AGENT=off`, or on a platform the agent isn't built for, the
tests run as usual and one warning says that issues aren't checked. Query
counts need no agent, so `maxQueries` still works. `TRACEPANEL_ENABLED=0`
turns the extension off.
