- name
- writing-tests
- description
- Write IntelliJ JUnit 5 tests with fixtures, lifecycle, EDT, and registry.
# Writing Tests
Guidelines for writing tests in IntelliJ IDEA codebase.
For examples, see `community/platform/testFramework/junit5/test/showcase/`.
## Place Tests in the Owning Module
Put a test in the test module associated with the production module it exercises. Do not place it in a downstream module merely because
that module has the production module on its test classpath. Check the production module's `.iml` file and neighboring tests before adding
a new test.
In particular, code in `org.jetbrains.intellij.build.io` under `community/build/tasks` belongs to
`intellij.idea.community.build.tasks.tests` (`community/build/tasks/test`), not `intellij.platform.buildScripts.tests`. The
`BUILD_SCRIPTS_PLATFORM_TESTS` group deliberately excludes `org.jetbrains.intellij.build.io.*` to avoid matching build-task tests by class
name across module boundaries. Putting such a test in the build-scripts test module leaves it outside every community test group and causes
`UltimateProjectTestsStructureTest` to fail.
## Prefer JUnit 5 over JUnit 4
Use JUnit 5 with `@TestApplication` annotation instead of extending `LightJavaCodeInsightFixtureTestCase`.
**Why JUnit 5:**
- **Faster**: No class hierarchy overhead, shared fixtures via companion objects
- **Cleaner**: Annotations (`@TestDisposable`, `@RegistryKey`) instead of manual setup/teardown
- **Flexible**: Mix EDT and non-EDT tests in one class, parameterized tests, nested tests
- **Better isolation**: Each test gets fresh disposables automatically
## Shared Fixtures Pattern
Use companion object fixtures shared between all tests:
```kotlin
@TestApplication
internal class MyTest {
companion object {
private val projectFixture = projectFixture()
private val moduleFixture = projectFixture.moduleFixture("src")
}
private val project get() = projectFixture.get()
private val module get() = moduleFixture.get()
}
```
## Lifecycle Hooks
Use JUnit 5 lifecycle annotations for setup and teardown:
```kotlin
@TestApplication
internal class MyTest {
companion object {
@JvmStatic
@BeforeAll
fun setUpClass() {
// Once before all tests in class
}
@JvmStatic
@AfterAll
fun tearDownClass() {
// Once after all tests in class
}
}
@BeforeEach
fun setUp() {
// Before each test method
}
@AfterEach
fun tearDown() {
// After each test method
}
}
```
**Note:** Prefer `@TestDisposable` over manual `@AfterEach` cleanup for resources.
## Test Disposables
Use `@TestDisposable` annotation to inject test-scoped disposables (created before each test, disposed after):
```kotlin
@TestDisposable
lateinit var disposable: Disposable
// Or as parameter
@Test
fun myTest(@TestDisposable disposable: Disposable) { ... }
```
## Registry Values in Tests
Use `@RegistryKey` annotation instead of `Registry.get().setValue()`:
```kotlin
@Test
@RegistryKey(key = "my.registry.key", value = "true")
fun testWithRegistryEnabled() { ... }
```
## System Properties in Tests
Use `@SystemProperty` annotation instead of `System.setProperty()`:
```kotlin
@Test
@SystemProperty(propertyKey = "my.property", propertyValue = "value")
fun testWithSystemProperty() { ... }
```
## Coroutines and UI Tests
Use `com.intellij.testFramework.common.timeoutRunBlocking` as the coroutine boundary and add a 30-second JUnit
`@Timeout`. `timeoutRunBlocking` has a 10-second default timeout, so a suspended test fails quickly with a coroutine-aware thread dump.
Keep setup, background work, and assertions off the UI thread. Move only Swing operations into a small
`withContext(Dispatchers.UI)` block:
```kotlin
@Test
@Timeout(30)
fun updatesLabel(): Unit = timeoutRunBlocking {
val value = loadValue()
val actual = withContext(Dispatchers.UI) {
label.text = value
label.text
}
assertThat(actual).isEqualTo(value)
}
```
`Dispatchers.UI` is strict: it supplies UI-thread affinity without implicit model access. Use `Dispatchers.EDT` only when the tested
operation genuinely requires model or lock access and cannot be split from the UI operation. Keep write actions explicit.
Editor creation/disposal, editor document mutation, completion invocation, action-group expansion, and file-editor operations are common
model-backed exceptions. Keep their full synchronous lifecycle in a narrow `Dispatchers.EDT` block; do not construct on strict UI and
dispose later from a different dispatcher.
Do not use `@RunInEdt`, `@RunMethodInEdt`, `runInEdtAndWait`, or `runInEdtAndGet` in new tests. They hide the coroutine boundary,
move lifecycle methods and assertions onto EDT, and can accidentally grant model or write-intent access.
When a synchronous callback API cannot call a suspending function, use a narrow bounded adapter around that callback only:
```kotlin
timeoutRunBlocking(timeout = 10.seconds, context = Dispatchers.UI) {
createSwingComponent()
}
```
Prefer observable completion signals, flows, latches, or virtual time over sleeps. If polling is unavoidable, bound it and make the
predicate suspending so UI checks can use `withContext(Dispatchers.UI)` without nested blocking.
Use the enclosing test scope for launched work: pass `this` from `timeoutRunBlocking`/`coroutineScope`, or `backgroundScope` from
`runTest`, into the code under test. Create a standalone `CoroutineScope(...)` only when independent scope lifetime is itself under test;
parent it to the test job and cancel it in `finally`. Use `delay` freely with `runTest` virtual time, but do not use a real timing-only
delay as a completion signal.
## Key Classes
- `com.intellij.testFramework.junit5.TestApplication` - initializes shared application
- `com.intellij.testFramework.junit5.TestDisposable` - injects test disposables
- `com.intellij.testFramework.junit5.RegistryKey` - sets registry values
- `com.intellij.testFramework.junit5.SystemProperty` - sets system properties
- `com.intellij.testFramework.common.timeoutRunBlocking` - runs suspending test code with a 10-second default timeout
- `com.intellij.testFramework.junit5.fixture.projectFixture` - creates project fixtures
- `com.intellij.testFramework.junit5.fixture.moduleFixture` - creates module fixtures
## Showcase Tests
- `JUnit5ProjectFixtureTest.kt` - project fixture patterns
- `JUnit5DisposableTest.kt` - disposable injection
- `JUnit5SystemPropertyTest.kt` - system property usage
- `JUnit5RunInEdtTest.java` - legacy EDT extension behavior; do not copy it for new tests
## Running Tests
To run tests via command line, see [TESTING.md](../testing/SKILL.md).
Quick example:
```bash
./tests.cmd --module <test-module> --test com.example.MyTest
```
Auf GitHub ansehen