원클릭으로
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