0
# Package-Level Functions
1
2
Global utility functions and implicit classes available at the package level for common testing operations and time dilation.
3
4
## Capabilities
5
6
### Event Filtering Functions
7
8
Package-level functions for convenient event filtering operations.
9
10
```scala { .api }
11
package object testkit {
12
// Event filtering with multiple filters
13
def filterEvents[T](eventFilters: Iterable[EventFilter])(block: => T)(implicit system: ActorSystem): T
14
def filterEvents[T](eventFilters: EventFilter*)(block: => T)(implicit system: ActorSystem): T
15
16
// Exception filtering shorthand
17
def filterException[T <: Throwable](block: => Unit)(implicit system: ActorSystem, t: ClassTag[T]): Unit
18
}
19
```
20
21
**Usage Examples:**
22
23
```scala
24
import akka.testkit._
25
26
class PackageFunctionsTest extends TestKit(ActorSystem("TestSystem")) {
27
"Package-level filterEvents" should {
28
"filter multiple event types" in {
29
filterEvents(
30
EventFilter.error(occurrences = 1),
31
EventFilter.warning(pattern = "deprecated".r, occurrences = 2),
32
EventFilter[RuntimeException](occurrences = 1)
33
) {
34
val actor = system.actorOf(Props(new Actor {
35
def receive = {
36
case "error" => log.error("Test error")
37
case "warn" =>
38
log.warning("Use of deprecated feature")
39
log.warning("Another deprecated warning")
40
case "exception" => throw new RuntimeException("Test exception")
41
}
42
}))
43
44
actor ! "error"
45
actor ! "warn"
46
actor ! "exception"
47
}
48
}
49
50
"work with iterable filters" in {
51
val filters = List(
52
EventFilter.info(message = "Starting", occurrences = 1),
53
EventFilter.info(message = "Completed", occurrences = 1)
54
)
55
56
filterEvents(filters) {
57
val actor = system.actorOf(Props(new Actor {
58
def receive = {
59
case "process" =>
60
log.info("Starting")
61
// Do work
62
log.info("Completed")
63
}
64
}))
65
66
actor ! "process"
67
}
68
}
69
}
70
71
"Package-level filterException" should {
72
"provide shorthand for exception filtering" in {
73
filterException[IllegalArgumentException] {
74
val actor = system.actorOf(Props(new Actor {
75
def receive = {
76
case invalid: String if invalid.isEmpty =>
77
throw new IllegalArgumentException("Empty string not allowed")
78
}
79
}))
80
81
actor ! ""
82
}
83
}
84
85
"work with type inference" in {
86
import scala.reflect.ClassTag
87
88
def testException[E <: Throwable : ClassTag](triggerException: => Unit): Unit = {
89
filterException[E] {
90
triggerException
91
}
92
}
93
94
testException[NullPointerException] {
95
throw new NullPointerException("Test NPE")
96
}
97
}
98
}
99
}
100
```
101
102
### Time Dilation Implicit Class
103
104
Implicit class for scaling durations based on test environment.
105
106
```scala { .api }
107
implicit class TestDuration(val duration: FiniteDuration) extends AnyVal {
108
def dilated(implicit system: ActorSystem): FiniteDuration
109
}
110
```
111
112
**Usage Examples:**
113
114
```scala
115
import akka.testkit._
116
import scala.concurrent.duration._
117
118
class TimeDilationTest extends TestKit(ActorSystem("TestSystem")) {
119
"TestDuration implicit class" should {
120
"dilate durations for test reliability" in {
121
val originalTimeout = 1.second
122
val dilatedTimeout = originalTimeout.dilated
123
124
// Dilated timeout accounts for slow test environments
125
dilatedTimeout should be >= originalTimeout
126
127
// Use in expectations
128
within(5.seconds.dilated) {
129
// Code that might run slowly in CI
130
Thread.sleep(100)
131
expectNoMessage(500.millis.dilated)
132
}
133
}
134
135
"work with various duration types" in {
136
// All duration types work
137
val millis = 100.millis.dilated
138
val seconds = 2.seconds.dilated
139
val minutes = 1.minute.dilated
140
141
millis should be >= 100.millis
142
seconds should be >= 2.seconds
143
minutes should be >= 1.minute
144
}
145
146
"respect test time factor configuration" in {
147
// Time factor configured in application.conf: akka.test.timefactor = 2.0
148
val original = 1.second
149
val dilated = original.dilated
150
151
// With timefactor = 2.0, dilated should be ~2x original
152
val settings = TestKitExtension(system)
153
val expectedDilated = Duration.fromNanos((original.toNanos * settings.TestTimeFactor).toLong)
154
155
dilated should be(expectedDilated)
156
}
157
158
"be used in actor timing tests" in {
159
class TimedActor extends Actor {
160
import context.dispatcher
161
162
def receive = {
163
case "delayed-response" =>
164
val originalSender = sender()
165
context.system.scheduler.scheduleOnce(200.millis.dilated) {
166
originalSender ! "response"
167
}
168
}
169
}
170
171
val actor = system.actorOf(Props[TimedActor]())
172
actor ! "delayed-response"
173
174
// Wait with dilated timeout to account for test environment
175
expectMsg(1.second.dilated, "response")
176
}
177
}
178
}
179
```
180
181
### Integration with TestKit Methods
182
183
Package-level functions integrate seamlessly with TestKit methods:
184
185
```scala
186
class IntegrationTest extends TestKit(ActorSystem("TestSystem")) with ImplicitSender {
187
"Package functions with TestKit" should {
188
"combine with expectation methods" in {
189
val actor = system.actorOf(Props(new Actor {
190
def receive = {
191
case "test" =>
192
log.info("Processing test message")
193
sender() ! "processed"
194
}
195
}))
196
197
filterEvents(EventFilter.info(message = "Processing test message")) {
198
actor ! "test"
199
expectMsg(3.seconds.dilated, "processed")
200
}
201
}
202
203
"work with awaitCond" in {
204
@volatile var completed = false
205
206
val actor = system.actorOf(Props(new Actor {
207
def receive = {
208
case "complete" =>
209
Thread.sleep(100) // Simulate work
210
completed = true
211
}
212
}))
213
214
actor ! "complete"
215
awaitCond(completed, max = 2.seconds.dilated)
216
217
assert(completed)
218
}
219
220
"combine with within blocks" in {
221
within(1.second.dilated) {
222
filterEvents(EventFilter.debug(start = "Debug message")) {
223
val actor = system.actorOf(Props(new Actor {
224
def receive = {
225
case msg =>
226
log.debug(s"Debug message: $msg")
227
sender() ! s"handled: $msg"
228
}
229
}))
230
231
actor ! "test"
232
expectMsg("handled: test")
233
}
234
}
235
}
236
}
237
}
238
```
239
240
### Advanced Usage Patterns
241
242
**Nested Event Filtering:**
243
244
```scala
245
class NestedFilteringTest extends TestKit(ActorSystem("TestSystem")) {
246
"Nested event filtering" should {
247
"work with multiple layers" in {
248
filterEvents(EventFilter.error(occurrences = 1)) {
249
filterEvents(EventFilter.warning(occurrences = 2)) {
250
val actor = system.actorOf(Props(new Actor {
251
def receive = {
252
case "process" =>
253
log.warning("First warning")
254
log.warning("Second warning")
255
log.error("An error occurred")
256
}
257
}))
258
259
actor ! "process"
260
}
261
}
262
}
263
}
264
}
265
```
266
267
**Dynamic Filter Creation:**
268
269
```scala
270
class DynamicFilterTest extends TestKit(ActorSystem("TestSystem")) {
271
"Dynamic filter creation" should {
272
"create filters based on test parameters" in {
273
def testWithFilters(errorCount: Int, warningCount: Int): Unit = {
274
val filters = Seq(
275
if (errorCount > 0) Some(EventFilter.error(occurrences = errorCount)) else None,
276
if (warningCount > 0) Some(EventFilter.warning(occurrences = warningCount)) else None
277
).flatten
278
279
filterEvents(filters) {
280
val actor = system.actorOf(Props(new Actor {
281
def receive = {
282
case (errors: Int, warnings: Int) =>
283
(1 to errors).foreach(_ => log.error("Error"))
284
(1 to warnings).foreach(_ => log.warning("Warning"))
285
}
286
}))
287
288
actor ! (errorCount, warningCount)
289
}
290
}
291
292
testWithFilters(2, 3)
293
testWithFilters(0, 1)
294
testWithFilters(1, 0)
295
}
296
}
297
}
298
```
299
300
### Configuration and Environment
301
302
Package functions respect TestKit configuration:
303
304
```hocon
305
# application.conf
306
akka {
307
test {
308
# Time dilation factor - all dilated durations multiplied by this
309
timefactor = 1.0
310
311
# Event filter leeway - extra time to wait for expected log events
312
filter-leeway = 3s
313
314
# Default timeouts
315
single-expect-default = 3s
316
multi-expect-default = 3s
317
}
318
}
319
```
320
321
**Accessing Configuration:**
322
323
```scala
324
class ConfigurationAccessTest extends TestKit(ActorSystem("TestSystem")) {
325
"Configuration access" should {
326
"use configured time factors" in {
327
val settings = TestKitExtension(system)
328
val timeFactor = settings.TestTimeFactor
329
330
val original = 1.second
331
val dilated = original.dilated
332
val expected = Duration.fromNanos((original.toNanos * timeFactor).toLong)
333
334
dilated should be(expected)
335
}
336
}
337
}
338
```
339
340
### Best Practices
341
342
1. **Always Use Dilated Timeouts**: Use `.dilated` for all test timeouts to handle slow environments
343
2. **Specific Event Filtering**: Be specific with event filters to avoid false positives
344
3. **Combine Functions**: Mix package functions with TestKit methods for comprehensive testing
345
4. **Environment Awareness**: Configure appropriate time factors for different test environments
346
347
```scala
348
// Good: Dilated timeouts for reliability
349
expectMsg(5.seconds.dilated, expectedMessage)
350
awaitCond(condition, max = 3.seconds.dilated)
351
352
// Good: Specific event filtering
353
filterEvents(
354
EventFilter.error(source = "com.myapp.Service", occurrences = 1),
355
EventFilter.warning(pattern = "deprecated.*method".r, occurrences = 1)
356
) {
357
// test code
358
}
359
360
// Good: Combined with TestKit methods
361
within(10.seconds.dilated) {
362
filterException[IllegalStateException] {
363
actor ! InvalidCommand()
364
}
365
}
366
```