0
# Reflection Matchers
1
2
JVM reflection-based matchers for classes, annotations, properties, and method validation using Kotlin reflection API for comprehensive type and metadata assertion capabilities.
3
4
## Capabilities
5
6
### Class Annotation Validation
7
8
Matchers for validating class-level annotations and annotation metadata.
9
10
```kotlin { .api }
11
/**
12
* Assert that class has at least one annotation
13
* @return The original KClass for chaining
14
*/
15
fun KClass<*>.shouldHaveAnnotations(): KClass<*>
16
17
/**
18
* Assert that class has no annotations
19
* @return The original KClass for chaining
20
*/
21
fun KClass<*>.shouldNotHaveAnnotations(): KClass<*>
22
23
/**
24
* Assert that class has exactly specified number of annotations
25
* @param count Expected number of annotations
26
* @return The original KClass for chaining
27
*/
28
infix fun KClass<*>.shouldHaveAnnotations(count: Int): KClass<*>
29
30
/**
31
* Assert that class does not have specified number of annotations
32
* @param count Annotation count that should not match
33
* @return The original KClass for chaining
34
*/
35
infix fun KClass<*>.shouldNotHaveAnnotations(count: Int): KClass<*>
36
37
/**
38
* Assert that class is annotated with specific annotation type
39
* @param T The annotation type to check for
40
* @param block Optional validation block for annotation properties
41
* @return The original KClass for chaining
42
*/
43
inline fun <reified T : Annotation> KClass<*>.shouldBeAnnotatedWith(
44
noinline block: (T) -> Unit = {}
45
): KClass<*>
46
47
/**
48
* Assert that class is not annotated with specific annotation type
49
* @param T The annotation type that should not be present
50
* @return The original KClass for chaining
51
*/
52
inline fun <reified T : Annotation> KClass<*>.shouldNotBeAnnotatedWith(): KClass<*>
53
54
/**
55
* Create matcher for annotation count validation
56
* @param count Expected number of annotations (-1 for any positive count)
57
* @return Matcher that passes when class has expected annotation count
58
*/
59
fun haveClassAnnotations(count: Int = -1): Matcher<KClass<*>>
60
61
/**
62
* Create matcher for specific annotation presence
63
* @param T The annotation type to check for
64
* @param block Optional validation block for annotation properties
65
* @return Matcher that passes when class has the specified annotation
66
*/
67
inline fun <reified T : Annotation> beAnnotatedWith(
68
noinline block: (T) -> Unit = {}
69
): Matcher<KClass<*>>
70
```
71
72
**Usage Examples:**
73
74
```kotlin
75
import io.kotest.matchers.reflection.*
76
import kotlin.reflect.KClass
77
78
@Entity
79
@Table(name = "users")
80
@Serializable
81
data class User(
82
@Id val id: Long,
83
@Column(name = "username") val name: String
84
)
85
86
val userClass: KClass<User> = User::class
87
88
// Annotation presence validation
89
userClass.shouldHaveAnnotations()
90
userClass.shouldHaveAnnotations(3) // Entity, Table, Serializable
91
92
// Specific annotation validation
93
userClass.shouldBeAnnotatedWith<Entity>()
94
userClass.shouldBeAnnotatedWith<Table> { table ->
95
table.name shouldBe "users"
96
}
97
98
// Using matcher syntax
99
userClass should haveClassAnnotations(3)
100
userClass should beAnnotatedWith<Serializable>()
101
```
102
103
### Property and Field Validation
104
105
Matchers for validating class properties, fields, and their characteristics.
106
107
```kotlin { .api }
108
/**
109
* Assert that class has property with specified name
110
* @param name The property name to check for
111
* @return The original KClass for chaining
112
*/
113
infix fun KClass<*>.shouldHaveProperty(name: String): KClass<*>
114
115
/**
116
* Assert that class does not have property with specified name
117
* @param name The property name that should not exist
118
* @return The original KClass for chaining
119
*/
120
infix fun KClass<*>.shouldNotHaveProperty(name: String): KClass<*>
121
122
/**
123
* Assert that class has exactly specified number of properties
124
* @param count Expected number of properties
125
* @return The original KClass for chaining
126
*/
127
infix fun KClass<*>.shouldHavePropertyCount(count: Int): KClass<*>
128
129
/**
130
* Assert that class has mutable property with specified name
131
* @param name The mutable property name to check for
132
* @return The original KClass for chaining
133
*/
134
infix fun KClass<*>.shouldHaveMutableProperty(name: String): KClass<*>
135
136
/**
137
* Assert that class has immutable property with specified name
138
* @param name The immutable property name to check for
139
* @return The original KClass for chaining
140
*/
141
infix fun KClass<*>.shouldHaveImmutableProperty(name: String): KClass<*>
142
143
/**
144
* Assert that property has specific type
145
* @param name Property name
146
* @param type Expected property type
147
* @return The original KClass for chaining
148
*/
149
fun KClass<*>.shouldHavePropertyOfType(name: String, type: KClass<*>): KClass<*>
150
151
/**
152
* Create matcher for property existence validation
153
* @param name The property name to check for
154
* @return Matcher that passes when class has the named property
155
*/
156
fun haveProperty(name: String): Matcher<KClass<*>>
157
158
/**
159
* Create matcher for property count validation
160
* @param count Expected number of properties
161
* @return Matcher that passes when class has exact property count
162
*/
163
fun havePropertyCount(count: Int): Matcher<KClass<*>>
164
165
/**
166
* Create matcher for mutable property validation
167
* @param name The mutable property name
168
* @return Matcher that passes when class has mutable property
169
*/
170
fun haveMutableProperty(name: String): Matcher<KClass<*>>
171
172
/**
173
* Create matcher for property type validation
174
* @param name Property name
175
* @param type Expected property type
176
* @return Matcher that passes when property has expected type
177
*/
178
fun havePropertyOfType(name: String, type: KClass<*>): Matcher<KClass<*>>
179
```
180
181
### Constructor and Function Validation
182
183
Matchers for validating class constructors and member functions.
184
185
```kotlin { .api }
186
/**
187
* Assert that class has primary constructor with specified parameter count
188
* @param parameterCount Expected number of constructor parameters
189
* @return The original KClass for chaining
190
*/
191
infix fun KClass<*>.shouldHavePrimaryConstructor(parameterCount: Int): KClass<*>
192
193
/**
194
* Assert that class does not have primary constructor
195
* @return The original KClass for chaining
196
*/
197
fun KClass<*>.shouldNotHavePrimaryConstructor(): KClass<*>
198
199
/**
200
* Assert that class has function with specified name
201
* @param name The function name to check for
202
* @return The original KClass for chaining
203
*/
204
infix fun KClass<*>.shouldHaveFunction(name: String): KClass<*>
205
206
/**
207
* Assert that class does not have function with specified name
208
* @param name The function name that should not exist
209
* @return The original KClass for chaining
210
*/
211
infix fun KClass<*>.shouldNotHaveFunction(name: String): KClass<*>
212
213
/**
214
* Assert that class has exactly specified number of member functions
215
* @param count Expected number of functions (excluding inherited)
216
* @return The original KClass for chaining
217
*/
218
infix fun KClass<*>.shouldHaveFunctionCount(count: Int): KClass<*>
219
220
/**
221
* Assert that function has specific parameter types
222
* @param functionName Name of function to validate
223
* @param parameterTypes Expected parameter types in order
224
* @return The original KClass for chaining
225
*/
226
fun KClass<*>.shouldHaveFunctionWithParameters(
227
functionName: String,
228
vararg parameterTypes: KClass<*>
229
): KClass<*>
230
231
/**
232
* Create matcher for primary constructor validation
233
* @param parameterCount Expected parameter count
234
* @return Matcher that passes when class has primary constructor with parameter count
235
*/
236
fun havePrimaryConstructor(parameterCount: Int): Matcher<KClass<*>>
237
238
/**
239
* Create matcher for function existence validation
240
* @param name The function name to check for
241
* @return Matcher that passes when class has the named function
242
*/
243
fun haveFunction(name: String): Matcher<KClass<*>>
244
245
/**
246
* Create matcher for function count validation
247
* @param count Expected number of member functions
248
* @return Matcher that passes when class has exact function count
249
*/
250
fun haveFunctionCount(count: Int): Matcher<KClass<*>>
251
252
/**
253
* Create matcher for function signature validation
254
* @param functionName Name of function
255
* @param parameterTypes Expected parameter types
256
* @return Matcher that passes when function has expected signature
257
*/
258
fun haveFunctionWithParameters(
259
functionName: String,
260
vararg parameterTypes: KClass<*>
261
): Matcher<KClass<*>>
262
```
263
264
**Usage Examples:**
265
266
```kotlin
267
import io.kotest.matchers.reflection.*
268
import kotlin.reflect.KClass
269
270
data class Person(
271
val id: Long,
272
var name: String,
273
val email: String
274
) {
275
fun updateName(newName: String) {
276
name = newName
277
}
278
279
fun isValid(): Boolean = name.isNotEmpty() && email.contains("@")
280
}
281
282
val personClass: KClass<Person> = Person::class
283
284
// Property validation
285
personClass shouldHaveProperty "id"
286
personClass shouldHaveProperty "name"
287
personClass shouldHavePropertyCount 3
288
personClass shouldHaveMutableProperty "name"
289
personClass shouldHaveImmutableProperty "id"
290
personClass.shouldHavePropertyOfType("id", Long::class)
291
292
// Constructor validation
293
personClass shouldHavePrimaryConstructor 3
294
295
// Function validation
296
personClass shouldHaveFunction "updateName"
297
personClass shouldHaveFunction "isValid"
298
personClass shouldHaveFunctionCount 2
299
personClass.shouldHaveFunctionWithParameters("updateName", String::class)
300
```
301
302
### Class Type and Inheritance
303
304
Matchers for validating class types, inheritance relationships, and type characteristics.
305
306
```kotlin { .api }
307
/**
308
* Assert that class is abstract
309
* @return The original KClass for chaining
310
*/
311
fun KClass<*>.shouldBeAbstract(): KClass<*>
312
313
/**
314
* Assert that class is not abstract (concrete)
315
* @return The original KClass for chaining
316
*/
317
fun KClass<*>.shouldNotBeAbstract(): KClass<*>
318
319
/**
320
* Assert that class is a data class
321
* @return The original KClass for chaining
322
*/
323
fun KClass<*>.shouldBeDataClass(): KClass<*>
324
325
/**
326
* Assert that class is not a data class
327
* @return The original KClass for chaining
328
*/
329
fun KClass<*>.shouldNotBeDataClass(): KClass<*>
330
331
/**
332
* Assert that class is sealed
333
* @return The original KClass for chaining
334
*/
335
fun KClass<*>.shouldBeSealed(): KClass<*>
336
337
/**
338
* Assert that class is not sealed
339
* @return The original KClass for chaining
340
*/
341
fun KClass<*>.shouldNotBeSealed(): KClass<*>
342
343
/**
344
* Assert that class is an interface
345
* @return The original KClass for chaining
346
*/
347
fun KClass<*>.shouldBeInterface(): KClass<*>
348
349
/**
350
* Assert that class is not an interface
351
* @return The original KClass for chaining
352
*/
353
fun KClass<*>.shouldNotBeInterface(): KClass<*>
354
355
/**
356
* Assert that class is an enum class
357
* @return The original KClass for chaining
358
*/
359
fun KClass<*>.shouldBeEnum(): KClass<*>
360
361
/**
362
* Assert that class is not an enum class
363
* @return The original KClass for chaining
364
*/
365
fun KClass<*>.shouldNotBeEnum(): KClass<*>
366
367
/**
368
* Assert that class is subclass of specified parent class
369
* @param parent The parent class type
370
* @return The original KClass for chaining
371
*/
372
infix fun KClass<*>.shouldBeSubclassOf(parent: KClass<*>): KClass<*>
373
374
/**
375
* Assert that class is not subclass of specified class
376
* @param parent The class that should not be a parent
377
* @return The original KClass for chaining
378
*/
379
infix fun KClass<*>.shouldNotBeSubclassOf(parent: KClass<*>): KClass<*>
380
381
/**
382
* Create matcher for abstract class validation
383
* @return Matcher that passes for abstract classes
384
*/
385
fun beAbstract(): Matcher<KClass<*>>
386
387
/**
388
* Create matcher for data class validation
389
* @return Matcher that passes for data classes
390
*/
391
fun beDataClass(): Matcher<KClass<*>>
392
393
/**
394
* Create matcher for sealed class validation
395
* @return Matcher that passes for sealed classes
396
*/
397
fun beSealed(): Matcher<KClass<*>>
398
399
/**
400
* Create matcher for interface validation
401
* @return Matcher that passes for interfaces
402
*/
403
fun beInterface(): Matcher<KClass<*>>
404
405
/**
406
* Create matcher for enum validation
407
* @return Matcher that passes for enum classes
408
*/
409
fun beEnum(): Matcher<KClass<*>>
410
411
/**
412
* Create matcher for inheritance validation
413
* @param parent The parent class to check inheritance from
414
* @return Matcher that passes when class is subclass of parent
415
*/
416
fun beSubclassOf(parent: KClass<*>): Matcher<KClass<*>>
417
```
418
419
### Member Visibility and Modifiers
420
421
Matchers for validating member visibility and modifier characteristics.
422
423
```kotlin { .api }
424
/**
425
* Assert that class has public member with specified name
426
* @param memberName Name of member to check visibility for
427
* @return The original KClass for chaining
428
*/
429
infix fun KClass<*>.shouldHavePublicMember(memberName: String): KClass<*>
430
431
/**
432
* Assert that class has private member with specified name
433
* @param memberName Name of member to check visibility for
434
* @return The original KClass for chaining
435
*/
436
infix fun KClass<*>.shouldHavePrivateMember(memberName: String): KClass<*>
437
438
/**
439
* Assert that class has protected member with specified name
440
* @param memberName Name of member to check visibility for
441
* @return The original KClass for chaining
442
*/
443
infix fun KClass<*>.shouldHaveProtectedMember(memberName: String): KClass<*>
444
445
/**
446
* Assert that class has internal member with specified name
447
* @param memberName Name of member to check visibility for
448
* @return The original KClass for chaining
449
*/
450
infix fun KClass<*>.shouldHaveInternalMember(memberName: String): KClass<*>
451
452
/**
453
* Assert that function is suspend function
454
* @param functionName Name of function to check
455
* @return The original KClass for chaining
456
*/
457
infix fun KClass<*>.shouldHaveSuspendFunction(functionName: String): KClass<*>
458
459
/**
460
* Assert that function is inline function
461
* @param functionName Name of function to check
462
* @return The original KClass for chaining
463
*/
464
infix fun KClass<*>.shouldHaveInlineFunction(functionName: String): KClass<*>
465
466
/**
467
* Create matcher for public member validation
468
* @param memberName Name of member to check
469
* @return Matcher that passes when member is public
470
*/
471
fun havePublicMember(memberName: String): Matcher<KClass<*>>
472
473
/**
474
* Create matcher for private member validation
475
* @param memberName Name of member to check
476
* @return Matcher that passes when member is private
477
*/
478
fun havePrivateMember(memberName: String): Matcher<KClass<*>>
479
480
/**
481
* Create matcher for suspend function validation
482
* @param functionName Name of function to check
483
* @return Matcher that passes when function is suspend
484
*/
485
fun haveSuspendFunction(functionName: String): Matcher<KClass<*>>
486
```
487
488
**Usage Examples:**
489
490
```kotlin
491
import io.kotest.matchers.reflection.*
492
493
abstract class Shape {
494
abstract fun area(): Double
495
}
496
497
data class Circle(val radius: Double) : Shape() {
498
override fun area(): Double = Math.PI * radius * radius
499
}
500
501
interface Drawable {
502
fun draw()
503
}
504
505
enum class Color { RED, GREEN, BLUE }
506
507
sealed class Result<out T> {
508
data class Success<T>(val value: T) : Result<T>()
509
data class Error(val message: String) : Result<Nothing>()
510
}
511
512
// Class type validation
513
Shape::class.shouldBeAbstract()
514
Circle::class.shouldBeDataClass()
515
Circle::class shouldBeSubclassOf Shape::class
516
Drawable::class.shouldBeInterface()
517
Color::class.shouldBeEnum()
518
Result::class.shouldBeSealed()
519
520
// Using matcher syntax
521
Shape::class should beAbstract()
522
Circle::class should beDataClass()
523
Circle::class should beSubclassOf(Shape::class)
524
Drawable::class should beInterface()
525
```
526
527
### Generic Type Validation
528
529
Matchers for validating generic types and type parameters.
530
531
```kotlin { .api }
532
/**
533
* Assert that class has specified number of type parameters
534
* @param count Expected number of type parameters
535
* @return The original KClass for chaining
536
*/
537
infix fun KClass<*>.shouldHaveTypeParameterCount(count: Int): KClass<*>
538
539
/**
540
* Assert that class does not have type parameters (non-generic)
541
* @return The original KClass for chaining
542
*/
543
fun KClass<*>.shouldNotBeGeneric(): KClass<*>
544
545
/**
546
* Assert that class is generic (has type parameters)
547
* @return The original KClass for chaining
548
*/
549
fun KClass<*>.shouldBeGeneric(): KClass<*>
550
551
/**
552
* Create matcher for type parameter count validation
553
* @param count Expected number of type parameters
554
* @return Matcher that passes when class has expected type parameter count
555
*/
556
fun haveTypeParameterCount(count: Int): Matcher<KClass<*>>
557
558
/**
559
* Create matcher for generic class validation
560
* @return Matcher that passes for generic classes
561
*/
562
fun beGeneric(): Matcher<KClass<*>>
563
```
564
565
**Usage Examples:**
566
567
```kotlin
568
import io.kotest.matchers.reflection.*
569
570
class Container<T> {
571
var item: T? = null
572
}
573
574
class Repository<K, V> {
575
private val storage = mutableMapOf<K, V>()
576
}
577
578
class SimpleClass {
579
val value: String = ""
580
}
581
582
// Generic type validation
583
Container::class.shouldBeGeneric()
584
Container::class shouldHaveTypeParameterCount 1
585
Repository::class shouldHaveTypeParameterCount 2
586
SimpleClass::class.shouldNotBeGeneric()
587
588
// Using matcher syntax
589
Container::class should beGeneric()
590
Repository::class should haveTypeParameterCount(2)
591
```
592
593
## Error Handling
594
595
Reflection matchers provide detailed error information for assertion failures:
596
597
- **Annotation failures**: Show expected vs actual annotations with full annotation details
598
- **Property failures**: List all available properties when expected property is missing
599
- **Type failures**: Clear indication of expected vs actual class types and inheritance relationships
600
- **Visibility failures**: Specific information about member visibility and access modifiers
601
- **Generic failures**: Details about type parameters and generic constraints
602
- **Signature failures**: Complete function signatures with parameter types and return types
603
604
All reflection matchers handle edge cases like null types, missing members, and access restrictions while providing meaningful error messages that help debug reflection-based assertions.