-
Notifications
You must be signed in to change notification settings - Fork 10
API Best Practices
Our codebase uses JUnit 5. While most tests depend on Spring injection; this is not a requirement. For testing pure static utilities or MapStruct mappers, plain JUnit tests will often suffice (i.e. no Spring annotations on the test class).
Prefer the Google Truth library for assertions.
In cases of repetitive test execution/assertions, consider using a table-driven @ParameterizedTest
. In most
cases, you'll want to use the reflection-based custom arguments provider approach:
- Documentation
- examples: 1, 2
Our Spring tests make heavy use of Spring Dependency Injection.
A lack of basic knowledge on this subject will make it difficult to understand the test configuration. Minimally, it is
good to have a general understanding of what @Autowired
and @Bean
do.
- Annotate the test class with
@SpringJUnitConfig
- In most cases, create an inner
@TestConfiguration
class- Note: it is possible to get similar utility by applying import annotations to the test class directly, but splitting these annotations from other Test class annotations generally provides more clarity.
- Import concrete implementation dependencies
-
@Autowired
the subject of your test - If needed, mock any bean dependencies via
@MockBean
- If needed,
@Autowired
any mocks or dependencies which your test needs to directly access
Typical Spring unit test boilerplate:
@SpringJUnitConfig
public class MyTest {
@TestConfiguration
@Import({MyTestSubject.class, MyServiceImpl.class})
@MockBean({MyOtherService.class})
static class TestConfiguration {
@Bean
public MyBean myBeanIfNeeded() [
return new MyBean();
}
}
@Autowired private MyTestSubject myTestSubject;
@Autowired private MyOtherService mockMyOtherService;
@Test
public void testFoo() {
...
}
}
You need to provide beans to satisfy all transitive dependencies of the beans you are injecting into your test. Typically this means all dependencies of the subject under test.
Example scenario:
- I'm testing an API controller
- I
@Import
the controller class - I
@Import
or@MockBean
all dependencies to satisfy the@Autowired
controller constructor - or if no constructor, all@Autowired
instance fields. - For anything I
@Import
d, I@Import
or@MockBean
any of its dependencies - repeat
Spring will quickly complain about "cannot find bean" when running your test if it's missing a dependency. It will typically take many iterations to resolve all of these import issues.
To autowire DAOs into your test (these will use an in-memory H2 database), use @DataJpaTest
on your test class.
Note: in most cases this should subsume the functionality of @SpringJUnitConfig
and can replace that.
@DataJpaTest
public class MyTest {
@Autowired private WorkspaceDao workspaceDao;
...
In your Java code, avoid global functions for determining the current time. Instead, inject a
Clock
in the encapsulating service or controller. From tests, you can then stub out the current
time by injecting an instance of FakeClock
. The FakeClockConfiguration
sets this up for you.
To control the time, just inject the FakeClock
instance.
...
@TestConfiguration
@Import({FakeClockConfiguration.class})
static class TestConfiguration {}
@Autowired private FakeClock fakeClock;
@Test
public void myTest() {
fakeClock.set(TIME_0);
}
TODO: Add the error message here for when you forget to import FakeClockConfiguration
In a few cases, we use annotations to automatically set timestamps on our Hibernate models: @CreatedDate
, @LastModifiedDate
. In order to fake out the times for these interactions, import FakeJpaDateTimeConfiguration
- in addition to the usual FakeClockConfiguration
. Set the clock time
as described above.
...
@TestConfiguration
@Import({FakeClockConfiguration.class, FakeJpaDateTimeConfiguration.class})
static class TestConfiguration {}