Tracepanel

In tests

The JUnit extension runs each test as a trace and fails a test with an N+1, duplicate queries or too many queries.

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

Add the library to the test dependencies. With Gradle:

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

With Maven:

<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:

# 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:

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

It takes the issue 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

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:

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

@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:

RuleWhat it does
maxQueriesFails the test when it and its requests make more queries than this.
failOnReplaces the kinds of issues that fail the test.
allowTakes 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:

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

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.

On this page