Skip to main content

writing-tests

Write IntelliJ JUnit 5 tests with fixtures, lifecycle, EDT, and registry.

Quellinformationen

Repository
JetBrains/intellij-community
Letzte Quellaktivität
11. August 2026 um 09:52
Erkannte Sprache von SKILL.md
Englisch
Sterne
20.445
Forks
6.009

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
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