ワンクリックで
unit-test
Create unit tests or integration tests. Use when writing tests for business logic, commands, queries, handlers, or pure functions.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Create unit tests or integration tests. Use when writing tests for business logic, commands, queries, handlers, or pure functions.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Create a new HTMX form page with Thymeleaf template, UI handler, routes, and search. Use when adding a new create/edit form, list page, or CRUD flow for a domain entity.
Release a Helm chart (epistola or epistola-grafana) to a new version. Use when the user wants to release, publish, or cut a new Helm chart version.
Create a new release. Use when the user wants to release a new version, cut a release, or publish a version.
Review a branch/PR against epistola's architecture conventions and recorded preferences. Use when reviewing code changes; complements /code-review (which finds correctness bugs).
Create a Playwright UI test for browser-based testing. Use when testing HTMX interactions, form submissions, page navigation, or JavaScript behavior in the browser.
Create a new CQRS command or query with handler. Use when adding new business operations (state changes) or data retrieval logic.
| name | unit-test |
| description | Create unit tests or integration tests. Use when writing tests for business logic, commands, queries, handlers, or pure functions. |
Create tests for Epistola Suite code.
Input: What code or behavior to test, and whether it needs a unit test or integration test.
| What you're testing | Test type | Base class | Run with |
|---|---|---|---|
| Pure logic, algorithms, utilities | Unit test | None | ./gradlew unitTest |
| Commands, queries, DB operations | Integration test | CoreIntegrationTestBase | ./gradlew integrationTest |
| UI handlers, routes, HTTP | Integration test | BaseIntegrationTest | ./gradlew integrationTest |
| Browser interactions, HTMX | UI test | BasePlaywrightTest | ./gradlew uiTest |
| TypeScript engine logic | Vitest | N/A | pnpm --filter @epistola/editor test |
Tags (@Tag("unit"), @Tag("integration"), @Tag("ui")) are inherited from the base classes — do NOT add them manually.
Create co-located with the code being tested.
class SomeClassTest {
@Test
fun `describes behavior in backtick syntax`() {
val result = pureFunction(input)
assertThat(result).isEqualTo(expected)
}
}
Conventions: No Spring context, no Testcontainers. Instantiate directly. Test input/output. Avoid mocking. Use AssertJ or kotlin.test.
Create in modules/epistola-core/src/test/kotlin/app/epistola/suite/<domain>/.
fixture { } DSLclass FeatureTest : CoreIntegrationTestBase() {
@Test
fun `CreateEntity creates entity with valid data`() = fixture {
whenever {
CreateEntity(id = ..., tenantId = tenantId, name = "Test").execute()
}
then {
val entity = result<Entity>()
assertThat(entity.name).isEqualTo("Test")
}
}
@Test
fun `ListEntities filters by search term`() = fixture {
given {
CreateEntity(..., name = "Alpha").execute()
CreateEntity(..., name = "Beta").execute()
}
whenever {
ListEntities(tenantId = tenantId, searchTerm = "Alpha").query()
}
then {
val results = result<List<Entity>>()
assertThat(results).hasSize(1)
}
}
}
given { } — preconditions (create entities)whenever { } — action under testthen { } — assertions via result<T>() to get the whenever return value@BeforeEachscenario { } DSL (type-safe)Reference: Check existing tests for scenario { } usage in modules/epistola-core/src/test/
The scenario DSL provides type-safe Given-When-Then with automatic cleanup:
@Test
fun `scenario example`() = scenario {
given("a template exists") {
CreateDocumentTemplate(...).execute()
}
whenever("we create a variant") {
CreateVariant(...).execute()
}
then("variant is returned") { result ->
assertThat(result.id).isNotNull()
}
}
withMediator { } for simple tests@Test
fun `simple test`() = withMediator {
val entity = CreateEntity(...).execute()
assertThat(entity.name).isEqualTo("Expected")
}
withAuthentication { } for security contextOnly available in CoreIntegrationTestBase. Use when the code under test checks the authenticated user:
@Test
fun `authenticated test`() = withAuthentication {
val entity = CreateEntity(...).execute()
// security context is bound
}
Reference: modules/epistola-core/src/test/kotlin/app/epistola/suite/common/TestIdHelpers.kt
Generates sequential unique IDs for tests:
TestIdHelpers.nextTemplateId() → TemplateId("test-template-1")TestIdHelpers.nextVariantId() → VariantId("test-variant-1")TestIdHelpers.nextEnvironmentId() → EnvironmentId("test-env-1")TestIdHelpers.nextAttributeId() → AttributeId("test-attr-1")TestIdHelpers.reset() — resets all counters (called automatically in @BeforeEach)@Nested and @ParameterizedTestUse @Nested inner classes to group related tests:
class ThemeCommandsTest : CoreIntegrationTestBase() {
@Nested
inner class CreateThemeTests {
@Test
fun `creates theme with valid data`() = fixture { ... }
@Test
fun `rejects duplicate id`() = fixture { ... }
}
@Nested
inner class DeleteThemeTests {
@Test
fun `deletes existing theme`() = fixture { ... }
}
}
Use @ParameterizedTest for testing multiple inputs:
@ParameterizedTest
@ValueSource(strings = ["", " ", " "])
fun `rejects blank name`(name: String) = withMediator {
assertThatThrownBy { CreateEntity(..., name = name).execute() }
.isInstanceOf(ValidationException::class.java)
}
Create in apps/epistola/src/test/kotlin/app/epistola/suite/<domain>/.
Reference: Existing *RoutesTest.kt files in apps/epistola/src/test/
class EntityRoutesTest : BaseIntegrationTest() {
@Autowired
private lateinit var restTemplate: TestRestTemplate
@Test
fun `GET entity list returns page`() = fixture {
lateinit var tenant: Tenant
given {
tenant = createTenant("Test Tenant")
}
whenever {
restTemplate.getForEntity("/tenants/${tenant.id}/entities", String::class.java)
}
then {
val response = result<ResponseEntity<String>>()
assertThat(response.statusCode).isEqualTo(HttpStatus.OK)
assertThat(response.body).contains("Expected Content")
}
}
}
BaseIntegrationTest already has @SpringBootTest(webEnvironment = RANDOM_PORT) — do NOT re-specify it on the test class.
Create co-located as <name>.test.ts:
import { describe, it, expect, vi } from "vitest";
import { SomeClass } from "./<source>.js";
describe("SomeClass", () => {
it("does something", () => {
const result = someFunction(input);
expect(result).toBe(expected);
});
});
Import with .js extension. Use vi.fn() for callbacks/spies. Use test-helpers.ts for engine fixtures.
./gradlew unitTest # Fast, no Docker
./gradlew integrationTest # Spring + DB (Docker required)
./gradlew uiTest # Playwright browser tests
./gradlew test # All Kotlin tests
./gradlew integrationTest --tests 'ClassName' # Specific test class
pnpm --filter @epistola/editor test # TypeScript tests
@BeforeEach — no need to manually clean upfixture { }, withMediator { }, and scenario { } all bind the MediatorContext — use one, not bothwithMediator { } at the call site — the mediator context is thread-localwithAuthentication { } is only on CoreIntegrationTestBase, not BaseIntegrationTest