0
# ArchUnit JUnit 5 Engine
1
2
ArchUnit JUnit 5 Engine provides seamless integration between ArchUnit architecture testing and JUnit 5 test suites. This engine enables automatic discovery and execution of ArchUnit architecture tests as part of the JUnit Platform, allowing architectural constraints to be validated during regular test execution with full IDE and build tool support.
3
4
## Package Information
5
6
- **Package Name**: com.tngtech.archunit:archunit-junit5-engine
7
- **Package Type**: Maven
8
- **Language**: Java
9
- **Installation**: Add to test dependencies in build file
10
11
### Gradle
12
```gradle
13
testImplementation 'com.tngtech.archunit:archunit-junit5-engine:1.4.1'
14
```
15
16
### Maven
17
```xml
18
<dependency>
19
<groupId>com.tngtech.archunit</groupId>
20
<artifactId>archunit-junit5-engine</artifactId>
21
<version>1.4.1</version>
22
<scope>test</scope>
23
</dependency>
24
```
25
26
## Core Imports
27
28
```java
29
import com.tngtech.archunit.junit.AnalyzeClasses;
30
import com.tngtech.archunit.junit.ArchTest;
31
import com.tngtech.archunit.junit.ArchIgnore;
32
import com.tngtech.archunit.junit.ArchTag;
33
import com.tngtech.archunit.junit.ArchTests;
34
import com.tngtech.archunit.junit.CacheMode;
35
import com.tngtech.archunit.junit.LocationProvider;
36
import com.tngtech.archunit.junit.engine_api.FieldSelector;
37
import com.tngtech.archunit.junit.engine_api.FieldSource;
38
import com.tngtech.archunit.lang.ArchRule;
39
import com.tngtech.archunit.core.domain.JavaClasses;
40
import com.tngtech.archunit.core.importer.ImportOption;
41
import com.tngtech.archunit.core.importer.Location;
42
import java.util.Set;
43
import java.lang.reflect.Field;
44
```
45
46
## Basic Usage
47
48
```java
49
import com.tngtech.archunit.core.domain.JavaClasses;
50
import com.tngtech.archunit.junit.AnalyzeClasses;
51
import com.tngtech.archunit.junit.ArchTest;
52
import com.tngtech.archunit.lang.ArchRule;
53
54
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.*;
55
56
@AnalyzeClasses(packages = "com.myapp")
57
public class ArchitectureTest {
58
59
@ArchTest
60
public static final ArchRule layered_architecture =
61
layeredArchitecture()
62
.layer("Controller").definedBy("..controller..")
63
.layer("Service").definedBy("..service..")
64
.layer("Repository").definedBy("..repository..")
65
.whereLayer("Controller").mayNotBeAccessedByAnyLayer()
66
.whereLayer("Service").mayOnlyBeAccessedByLayers("Controller")
67
.whereLayer("Repository").mayOnlyBeAccessedByLayers("Service");
68
69
@ArchTest
70
public void services_should_only_be_accessed_by_controllers(JavaClasses classes) {
71
classes().that().resideInAPackage("..service..")
72
.should().onlyBeAccessed().byAnyPackage("..controller..", "..service..")
73
.check(classes);
74
}
75
}
76
```
77
78
## Architecture
79
80
The ArchUnit JUnit 5 Engine is built around several key components:
81
82
- **Test Engine**: Core JUnit Platform integration via `ArchUnitTestEngine` registered as 'archunit' engine
83
- **Test Discovery**: Automatic scanning for `@AnalyzeClasses` annotated classes containing `@ArchTest` fields/methods
84
- **Test Execution**: Executes ArchUnit rules against imported Java classes with comprehensive error reporting
85
- **Descriptor Hierarchy**: Organizes tests into hierarchical structure (Engine → Class → Field/Method descriptors)
86
- **Caching System**: Optimizes performance by caching imported Java classes across test executions
87
- **Filtering Support**: Supports JUnit Platform test filtering and custom ArchUnit property-based filtering
88
89
## Capabilities
90
91
### Test Discovery and Selectors
92
93
Provides JUnit Platform discovery selectors for programmatic test selection beyond standard class/method selection.
94
95
```java { .api }
96
/**
97
* Selector for ArchUnit test fields, enables programmatic discovery of specific @ArchTest fields
98
*/
99
public final class FieldSelector implements DiscoverySelector {
100
/**
101
* Select a field by class name and field name
102
* @param className - Fully qualified class name containing the field
103
* @param fieldName - Name of the @ArchTest field to select
104
* @return FieldSelector for the specified field
105
*/
106
public static FieldSelector selectField(String className, String fieldName);
107
108
/**
109
* Select a field by class and field name
110
* @param javaClass - Class containing the field
111
* @param fieldName - Name of the @ArchTest field to select
112
* @return FieldSelector for the specified field
113
*/
114
public static FieldSelector selectField(Class<?> javaClass, String fieldName);
115
116
/**
117
* Select a field by class and field instance
118
* @param javaClass - Class containing the field
119
* @param field - The @ArchTest field to select
120
* @return FieldSelector for the specified field
121
*/
122
public static FieldSelector selectField(Class<?> javaClass, Field field);
123
124
/** Get the Java class containing the selected field */
125
@Internal
126
public Class<?> getJavaClass();
127
128
/** Get the selected Java field */
129
@Internal
130
public Field getJavaField();
131
}
132
```
133
134
### Test Source Information
135
136
Provides detailed source information for ArchUnit test fields enabling IDE navigation and reporting.
137
138
```java { .api }
139
/**
140
* Test source implementation for ArchUnit test fields
141
*/
142
@PublicAPI(usage = ACCESS)
143
public final class FieldSource implements TestSource {
144
/**
145
* Create FieldSource from Field instance - internal use only
146
* @param field - The field to create source for
147
* @return FieldSource instance
148
*/
149
@Internal
150
static FieldSource from(Field field);
151
152
/** Get the fully qualified class name containing the field */
153
@PublicAPI(usage = ACCESS)
154
public String getClassName();
155
156
/** Get the Java class containing the field */
157
@PublicAPI(usage = ACCESS)
158
public Class<?> getJavaClass();
159
160
/** Get the field name */
161
@PublicAPI(usage = ACCESS)
162
public String getFieldName();
163
}
164
```
165
166
### Test Engine Registration
167
168
The engine automatically registers with JUnit Platform through service provider interface.
169
170
**Service Registration:**
171
- **File**: `META-INF/services/org.junit.platform.engine.TestEngine`
172
- **Implementation**: `com.tngtech.archunit.junit.internal.ArchUnitTestEngine`
173
- **Engine ID**: `"archunit"`
174
175
### Required Annotations
176
177
Tests must use these annotations from the core ArchUnit library:
178
179
```java { .api }
180
/**
181
* Specifies which packages/locations should be scanned when running JUnit 5 tests
182
*/
183
@Testable
184
@Target({TYPE})
185
@Retention(RUNTIME)
186
@PublicAPI(usage = ACCESS)
187
public @interface AnalyzeClasses {
188
/** Packages to look for within the classpath/modulepath */
189
String[] packages() default {};
190
191
/** Classes that specify packages to look for */
192
Class<?>[] packagesOf() default {};
193
194
/** Implementations of LocationProvider for custom class sources */
195
Class<? extends LocationProvider>[] locations() default {};
196
197
/** Whether to look for classes on the whole classpath */
198
boolean wholeClasspath() default false;
199
200
/** Types of ImportOption to use for filtering the class import */
201
Class<? extends ImportOption>[] importOptions() default {};
202
203
/** Controls if JavaClasses should be cached by location */
204
CacheMode cacheMode() default CacheMode.FOREVER;
205
}
206
207
/**
208
* Marks ArchUnit tests to be executed by the test infrastructure
209
*/
210
@Testable
211
@Target({FIELD, METHOD})
212
@Retention(RUNTIME)
213
public @interface ArchTest {
214
}
215
216
/**
217
* Marks rules to be ignored by the test support
218
*/
219
@Target({TYPE, FIELD, METHOD})
220
@Retention(RUNTIME)
221
public @interface ArchIgnore {
222
/** Why the test is ignored */
223
String reason() default "";
224
}
225
226
/**
227
* Repeatable annotation for tagging ArchTest fields/methods/classes
228
*/
229
@Inherited
230
@Documented
231
@Retention(RUNTIME)
232
@PublicAPI(usage = ACCESS)
233
@Repeatable(ArchTags.class)
234
@Target({TYPE, METHOD, FIELD})
235
public @interface ArchTag {
236
/** The actual tag value - must adhere to JUnit Platform tag syntax rules */
237
String value();
238
}
239
```
240
241
### Integration with ArchUnit Core
242
243
The engine integrates with core ArchUnit types and functionality:
244
245
```java { .api }
246
/**
247
* Collections of ArchUnit test rules for inclusion in test classes
248
*/
249
@PublicAPI(usage = ACCESS)
250
public final class ArchTests {
251
/**
252
* @param definitionLocation The class whose @ArchTest members should be included in this test
253
* @return the ArchTests of the supplied class
254
*/
255
@PublicAPI(usage = ACCESS)
256
public static ArchTests in(Class<?> definitionLocation);
257
}
258
259
/**
260
* Allows custom implementation to supply Locations to be imported by the JUnit test infrastructure
261
*/
262
@PublicAPI(usage = INHERITANCE)
263
public interface LocationProvider {
264
/**
265
* Returns locations to be imported for the current test run
266
* @param testClass The class object of the test currently executed
267
* @return The locations to import
268
*/
269
Set<Location> get(Class<?> testClass);
270
}
271
272
/**
273
* Determines how the JUnit test support caches classes
274
*/
275
@PublicAPI(usage = ACCESS)
276
public enum CacheMode {
277
/** Cache imported Java classes for current test class only */
278
@PublicAPI(usage = ACCESS)
279
PER_CLASS,
280
281
/** Cache imported Java classes by location using SoftReferences */
282
@PublicAPI(usage = ACCESS)
283
FOREVER
284
}
285
```
286
287
### Test Execution Patterns
288
289
**Field-based Tests (Recommended):**
290
```java
291
@AnalyzeClasses(packages = "com.example")
292
public class MyArchTest {
293
@ArchTest
294
public static final ArchRule rule = classes()
295
.that().resideInAPackage("..service..")
296
.should().notDependOn("..controller..");
297
}
298
```
299
300
**Method-based Tests:**
301
```java
302
@AnalyzeClasses(packages = "com.example")
303
public class MyArchTest {
304
@ArchTest
305
public void methodTest(JavaClasses classes) {
306
classes().that().resideInAPackage("..service..")
307
.should().notDependOn("..controller..")
308
.check(classes);
309
}
310
}
311
```
312
313
**Rule Libraries:**
314
```java
315
@AnalyzeClasses(packages = "com.example")
316
public class MyArchTest {
317
@ArchTest
318
public static final ArchTests rules = ArchTests.in(MyRuleLibrary.class);
319
}
320
```
321
322
### Test Filtering
323
324
**Property-based Filtering:**
325
```properties
326
# In archunit.properties or system property
327
junit.testFilter=specificRuleName,anotherRule
328
```
329
330
**JUnit Platform Filtering:**
331
Standard JUnit 5 filters work: class name filters, package filters, tags, etc.
332
333
## Error Handling
334
335
The engine provides comprehensive error reporting:
336
337
- **Initialization Errors**: Clear messages for missing `@AnalyzeClasses` annotations or invalid test method signatures
338
- **Rule Violations**: Detailed violation reports with class/method/field references and suggested fixes
339
- **Class Loading Issues**: Graceful handling of missing classes with informative error messages
340
- **Configuration Errors**: Validation of test method parameters (must accept single `JavaClasses` parameter)
341
342
## Performance Considerations
343
344
- **Class Cache**: Automatically caches imported `JavaClasses` to avoid repeated expensive import operations
345
- **Lazy Evaluation**: Test discovery and class analysis occur only when needed
346
- **Memory Management**: Cache is cleared per test class to prevent memory leaks
347
- **Concurrent Execution**: Thread-safe design supports parallel test execution
348
349
## Types
350
351
```java { .api }
352
// Key internal types (for understanding engine behavior)
353
interface CreatesChildren {
354
void createChildren(ElementResolver resolver);
355
}
356
357
// Test descriptor types extend JUnit Platform interfaces
358
abstract class AbstractArchUnitTestDescriptor extends AbstractTestDescriptor
359
implements Node<ArchUnitEngineExecutionContext>
360
361
class ArchUnitTestEngine extends HierarchicalTestEngine<ArchUnitEngineExecutionContext>
362
class ArchUnitEngineDescriptor extends EngineDescriptor
363
implements Node<ArchUnitEngineExecutionContext>
364
```