Skip to content

Commit e3bd957

Browse files
wakaleoclaude
andcommitted
docs: Document @nested class annotation inheritance for requirements hierarchy
Add documentation for inheriting @Epic/@Feature/@story annotations from enclosing classes in JUnit 5 @nested tests, including examples of full three-level hierarchies, annotation overriding, and @DisplayName fallback. Updated in English, Spanish (es), and Brazilian Portuguese (pt-BR). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 790b52a commit e3bd957

6 files changed

Lines changed: 444 additions & 0 deletions

File tree

docs/guide/annotation-requirements.md

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,117 @@ abstract class BaseTest { }
282282
class SpecificTest extends BaseTest { }
283283
```
284284

285+
## Inheriting Annotations in JUnit 5 @Nested Classes
286+
287+
When using JUnit 5 `@Nested` inner classes, annotations on the enclosing (outer) class are inherited by the nested class. This is a natural way to define the feature or epic once on the outer class and then define individual stories in each nested class:
288+
289+
```java
290+
@ExtendWith(SerenityJUnit5Extension.class)
291+
@Feature("Product Catalog")
292+
class ProductCatalogTests {
293+
294+
@Nested
295+
@Story("Searching by keyword")
296+
class WhenSearchingByKeyword {
297+
298+
@Test
299+
void shouldFindProductsByName() { /* ... */ }
300+
301+
@Test
302+
void shouldReturnEmptyResultsForUnknownKeyword() { /* ... */ }
303+
}
304+
305+
@Nested
306+
@Story("Browsing by category")
307+
class WhenBrowsingByCategory {
308+
309+
@Test
310+
void shouldListProductsInCategory() { /* ... */ }
311+
}
312+
}
313+
```
314+
315+
This produces the following hierarchy in the requirements report:
316+
317+
```
318+
Product Catalog (feature)
319+
├── Searching by keyword (story)
320+
│ ├── Should find products by name
321+
│ └── Should return empty results for unknown keyword
322+
└── Browsing by category (story)
323+
└── Should list products in category
324+
```
325+
326+
### Full Three-Level Hierarchy with Nested Classes
327+
328+
You can combine `@Epic` and `@Feature` on the outer class with `@Story` on nested classes for the full hierarchy:
329+
330+
```java
331+
@ExtendWith(SerenityJUnit5Extension.class)
332+
@Epic("E-Commerce Platform")
333+
@Feature("Shopping Cart")
334+
class ShoppingCartTests {
335+
336+
@Nested
337+
@Story("Add item to cart")
338+
class WhenAddingItems {
339+
@Test
340+
void shouldAddSingleItem() { /* ... */ }
341+
}
342+
343+
@Nested
344+
@Story("Remove item from cart")
345+
class WhenRemovingItems {
346+
@Test
347+
void shouldRemoveSelectedItem() { /* ... */ }
348+
}
349+
}
350+
```
351+
352+
This produces:
353+
354+
```
355+
E-Commerce Platform (epic)
356+
└── Shopping Cart (feature)
357+
├── Add item to cart (story)
358+
│ └── Should add single item
359+
└── Remove item from cart (story)
360+
└── Should remove selected item
361+
```
362+
363+
### Overriding Annotations in Nested Classes
364+
365+
If a nested class redefines an annotation that is already present on the outer class, the nested class annotation takes precedence:
366+
367+
```java
368+
@Feature("Product Catalog")
369+
class ProductTests {
370+
371+
@Nested
372+
@Feature("Checkout") // Overrides "Product Catalog"
373+
@Story("Express checkout")
374+
class WhenUsingExpressCheckout { /* ... */ }
375+
}
376+
```
377+
378+
### Using @DisplayName as Story Name in Nested Classes
379+
380+
When a nested class has no `@Story` annotation, its `@DisplayName` is used as the story name, just as it is for top-level classes:
381+
382+
```java
383+
@Feature("Product Catalog")
384+
class ProductCatalogTests {
385+
386+
@Nested
387+
@DisplayName("Searching by keyword") // Used as the story name
388+
class WhenSearchingByKeyword { /* ... */ }
389+
}
390+
```
391+
392+
:::tip Nested classes vs separate test classes
393+
Use `@Nested` classes when you want to group related stories under a single outer class and share setup code. Use separate top-level test classes when the stories are more independent. Both approaches produce the same requirements hierarchy in the reports.
394+
:::
395+
285396
## Annotations vs Package-Based Requirements
286397

287398
Annotation-based requirements **override** the default package-based hierarchy for the annotated test class. This means you can mix both approaches in the same project:

docs/junit5/junit5-tests.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,43 @@ class AuthenticationTests {
381381
}
382382
```
383383

384+
#### Nested Classes and Requirements Annotations
385+
386+
`@Nested` classes inherit `@Epic`, `@Feature`, and `@Story` annotations from their enclosing class. This lets you define the feature once on the outer class and assign individual stories to each nested class:
387+
388+
```java
389+
@ExtendWith(SerenityJUnit5Extension.class)
390+
@Feature("User Authentication")
391+
class AuthenticationTests {
392+
393+
@Nested
394+
@Story("Login")
395+
class WhenLoggingIn {
396+
@Test
397+
void shouldSucceedWithValidCredentials() { /* ... */ }
398+
}
399+
400+
@Nested
401+
@Story("Logout")
402+
class WhenLoggingOut {
403+
@Test
404+
void shouldClearSession() { /* ... */ }
405+
}
406+
}
407+
```
408+
409+
This produces the requirements hierarchy:
410+
411+
```
412+
User Authentication (feature)
413+
├── Login (story)
414+
│ └── Should succeed with valid credentials
415+
└── Logout (story)
416+
└── Should clear session
417+
```
418+
419+
See [Annotation-Based Requirements](/docs/guide/annotation-requirements#inheriting-annotations-in-junit-5-nested-classes) for the full details on annotation inheritance with nested classes.
420+
384421
## Parameterized Tests
385422

386423
### Simple Parameterized Tests

i18n/es/docusaurus-plugin-content-docs/current/guide/annotation-requirements.md

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,117 @@ abstract class BaseTest { }
282282
class SpecificTest extends BaseTest { }
283283
```
284284

285+
## Heredando Anotaciones en Clases @Nested de JUnit 5
286+
287+
Al usar clases internas `@Nested` de JUnit 5, las anotaciones de la clase contenedora (externa) son heredadas por la clase anidada. Esta es una forma natural de definir la funcionalidad o épica una sola vez en la clase externa y luego definir historias individuales en cada clase anidada:
288+
289+
```java
290+
@ExtendWith(SerenityJUnit5Extension.class)
291+
@Feature("Product Catalog")
292+
class ProductCatalogTests {
293+
294+
@Nested
295+
@Story("Searching by keyword")
296+
class WhenSearchingByKeyword {
297+
298+
@Test
299+
void shouldFindProductsByName() { /* ... */ }
300+
301+
@Test
302+
void shouldReturnEmptyResultsForUnknownKeyword() { /* ... */ }
303+
}
304+
305+
@Nested
306+
@Story("Browsing by category")
307+
class WhenBrowsingByCategory {
308+
309+
@Test
310+
void shouldListProductsInCategory() { /* ... */ }
311+
}
312+
}
313+
```
314+
315+
Esto produce la siguiente jerarquía en el informe de requisitos:
316+
317+
```
318+
Product Catalog (feature)
319+
├── Searching by keyword (story)
320+
│ ├── Should find products by name
321+
│ └── Should return empty results for unknown keyword
322+
└── Browsing by category (story)
323+
└── Should list products in category
324+
```
325+
326+
### Jerarquía Completa de Tres Niveles con Clases Anidadas
327+
328+
Puedes combinar `@Epic` y `@Feature` en la clase externa con `@Story` en las clases anidadas para la jerarquía completa:
329+
330+
```java
331+
@ExtendWith(SerenityJUnit5Extension.class)
332+
@Epic("E-Commerce Platform")
333+
@Feature("Shopping Cart")
334+
class ShoppingCartTests {
335+
336+
@Nested
337+
@Story("Add item to cart")
338+
class WhenAddingItems {
339+
@Test
340+
void shouldAddSingleItem() { /* ... */ }
341+
}
342+
343+
@Nested
344+
@Story("Remove item from cart")
345+
class WhenRemovingItems {
346+
@Test
347+
void shouldRemoveSelectedItem() { /* ... */ }
348+
}
349+
}
350+
```
351+
352+
Esto produce:
353+
354+
```
355+
E-Commerce Platform (epic)
356+
└── Shopping Cart (feature)
357+
├── Add item to cart (story)
358+
│ └── Should add single item
359+
└── Remove item from cart (story)
360+
└── Should remove selected item
361+
```
362+
363+
### Sobrescribiendo Anotaciones en Clases Anidadas
364+
365+
Si una clase anidada redefine una anotación que ya está presente en la clase externa, la anotación de la clase anidada tiene prioridad:
366+
367+
```java
368+
@Feature("Product Catalog")
369+
class ProductTests {
370+
371+
@Nested
372+
@Feature("Checkout") // Sobrescribe "Product Catalog"
373+
@Story("Express checkout")
374+
class WhenUsingExpressCheckout { /* ... */ }
375+
}
376+
```
377+
378+
### Usando @DisplayName como Nombre de Historia en Clases Anidadas
379+
380+
Cuando una clase anidada no tiene anotación `@Story`, su `@DisplayName` se usa como nombre de historia, igual que para las clases de nivel superior:
381+
382+
```java
383+
@Feature("Product Catalog")
384+
class ProductCatalogTests {
385+
386+
@Nested
387+
@DisplayName("Searching by keyword") // Se usa como nombre de historia
388+
class WhenSearchingByKeyword { /* ... */ }
389+
}
390+
```
391+
392+
:::tip Clases anidadas vs clases de prueba separadas
393+
Usa clases `@Nested` cuando quieras agrupar historias relacionadas bajo una sola clase externa y compartir código de configuración. Usa clases de prueba separadas de nivel superior cuando las historias sean más independientes. Ambos enfoques producen la misma jerarquía de requisitos en los informes.
394+
:::
395+
285396
## Anotaciones vs Requisitos Basados en Paquetes
286397

287398
Los requisitos basados en anotaciones **sobrescriben** la jerarquía basada en paquetes por defecto para la clase de prueba anotada. Esto significa que puedes mezclar ambos enfoques en el mismo proyecto:

i18n/es/docusaurus-plugin-content-docs/current/junit5/junit5-tests.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,43 @@ class AuthenticationTests {
381381
}
382382
```
383383

384+
#### Clases Anidadas y Anotaciones de Requisitos
385+
386+
Las clases `@Nested` heredan las anotaciones `@Epic`, `@Feature` y `@Story` de su clase contenedora. Esto te permite definir la funcionalidad una vez en la clase externa y asignar historias individuales a cada clase anidada:
387+
388+
```java
389+
@ExtendWith(SerenityJUnit5Extension.class)
390+
@Feature("User Authentication")
391+
class AuthenticationTests {
392+
393+
@Nested
394+
@Story("Login")
395+
class WhenLoggingIn {
396+
@Test
397+
void shouldSucceedWithValidCredentials() { /* ... */ }
398+
}
399+
400+
@Nested
401+
@Story("Logout")
402+
class WhenLoggingOut {
403+
@Test
404+
void shouldClearSession() { /* ... */ }
405+
}
406+
}
407+
```
408+
409+
Esto produce la jerarquía de requisitos:
410+
411+
```
412+
User Authentication (feature)
413+
├── Login (story)
414+
│ └── Should succeed with valid credentials
415+
└── Logout (story)
416+
└── Should clear session
417+
```
418+
419+
Consulta [Requisitos Basados en Anotaciones](/docs/guide/annotation-requirements#heredando-anotaciones-en-clases-nested-de-junit-5) para los detalles completos sobre la herencia de anotaciones con clases anidadas.
420+
384421
## Pruebas parametrizadas
385422

386423
### Pruebas parametrizadas simples

0 commit comments

Comments
 (0)