Skip to content

Commit 40d81d1

Browse files
ktosoyim-lee
andauthored
+testKit Add fishForMessages allowing selective expecting of msgs (#399)
* +testKit Add fishForMessages allowing selective expecting of msgs * Update Sources/DistributedActorsTestKit/TestProbes.swift Co-Authored-By: Yim Lee <[email protected]> Co-authored-by: Yim Lee <[email protected]>
1 parent b9afeb9 commit 40d81d1

File tree

3 files changed

+150
-0
lines changed

3 files changed

+150
-0
lines changed

Sources/DistributedActorsTestKit/TestProbes.swift

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,82 @@ extension ActorTestProbe {
194194

195195
throw ExpectationError.noMessagesInQueue
196196
}
197+
198+
/// Allows for "fishing out" certain messages from the stream of incoming messages to this probe.
199+
/// Messages can be caught or ignored using the passed in function.
200+
///
201+
/// Allows transforming the caught messages to `CaughtMessage` (e.g. extracting a specific payload from all incoming messages).
202+
/// If you need to aggregate the exact `Message` types, you prefer using `fishForMessages`.
203+
///
204+
/// Once `MessageFishingDirective.catchComplete` or `MessageFishingDirective.complete` is returned,
205+
/// the function returns all so-far accumulated messages.
206+
public func fishFor<CaughtMessage>(
207+
_ type: CaughtMessage.Type, within timeout: TimeAmount,
208+
file: StaticString = #file, line: UInt = #line, column: UInt = #column,
209+
_ fisher: (Message) throws -> FishingDirective<CaughtMessage>
210+
) throws -> [CaughtMessage] {
211+
let deadline = Deadline.fromNow(timeout)
212+
213+
var caughtMessages: [CaughtMessage] = []
214+
while deadline.hasTimeLeft() {
215+
if let message = try self.maybeExpectMessage(within: deadline.timeLeft) {
216+
switch try fisher(message) {
217+
case .catchContinue(let caught):
218+
caughtMessages.append(caught)
219+
case .catchComplete(let caught):
220+
caughtMessages.append(caught)
221+
return caughtMessages
222+
case .ignore:
223+
()
224+
case .complete:
225+
return caughtMessages
226+
}
227+
}
228+
}
229+
230+
return caughtMessages
231+
}
232+
233+
public enum FishingDirective<CaughtMessage> {
234+
case catchContinue(CaughtMessage)
235+
case catchComplete(CaughtMessage)
236+
case ignore
237+
case complete
238+
}
239+
240+
/// Allows for "fishing out" certain messages from the stream of incoming messages to this probe.
241+
/// Messages can be caught or ignored using the passed in function.
242+
///
243+
/// The accumulated messages are assumed to be transforming the caught messages to `CaughtMessage` (e.g. extracting a specific payload from all incoming messages).
244+
/// If you need to aggregate the exact `Message` types, you prefer using `fishForMessages`.
245+
///
246+
/// Once `MessageFishingDirective.catchComplete` or `MessageFishingDirective.complete` is returned,
247+
/// the function returns all so-far accumulated messages.
248+
public func fishForMessages(
249+
within timeout: TimeAmount,
250+
file: StaticString = #file, line: UInt = #line, column: UInt = #column,
251+
_ fisher: (Message) throws -> MessageFishingDirective
252+
) throws -> [Message] {
253+
try self.fishFor(Message.self, within: timeout, file: file, line: line, column: column) { message in
254+
switch try fisher(message) {
255+
case .catchContinue:
256+
return .catchContinue(message)
257+
case .catchComplete:
258+
return .catchComplete(message)
259+
case .ignore:
260+
return .ignore
261+
case .complete:
262+
return .complete
263+
}
264+
}
265+
}
266+
267+
public enum MessageFishingDirective {
268+
case catchContinue
269+
case catchComplete
270+
case ignore
271+
case complete
272+
}
197273
}
198274

199275
extension ActorTestProbe {

Tests/DistributedActorsTestKitTests/ActorTestKitTests+XCTest.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ extension ActorTestKitTests {
2828
("test_fail_shouldNotImmediatelyFailWithinEventuallyBlock", test_fail_shouldNotImmediatelyFailWithinEventuallyBlock),
2929
("test_nestedEventually_shouldProperlyHandleFailures", test_nestedEventually_shouldProperlyHandleFailures),
3030
("test_ensureRegistered_countAndRefs", test_ensureRegistered_countAndRefs),
31+
("test_fishForMessages", test_fishForMessages),
32+
("test_fishForTransformed", test_fishForTransformed),
33+
("test_fishFor_canThrow", test_fishFor_canThrow),
3134
("test_ActorableTestProbe_shouldWork", test_ActorableTestProbe_shouldWork),
3235
]
3336
}

Tests/DistributedActorsTestKitTests/ActorTestKitTests.swift

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,77 @@ final class ActorTestKitTests: XCTestCase {
8686
try self.testKit.ensureRegistered(key: key, expectedCount: 2, expectedRefs: [greeterProbe2.ref, greeterProbe1.ref])
8787
}
8888

89+
func test_fishForMessages() throws {
90+
let p = self.testKit.spawnTestProbe(expecting: String.self)
91+
92+
p.tell("yes-1")
93+
p.tell("yes-2")
94+
p.tell("no-1")
95+
p.tell("no-2")
96+
p.tell("yes-3")
97+
p.tell("yes-end")
98+
99+
let messages = try p.fishForMessages(within: .seconds(30)) { message in
100+
if message.contains("yes-end") {
101+
return .catchComplete
102+
} else if message.contains("yes") {
103+
return .catchContinue
104+
} else {
105+
return .ignore
106+
}
107+
}
108+
109+
messages.shouldEqual([
110+
"yes-1",
111+
"yes-2",
112+
"yes-3",
113+
"yes-end",
114+
])
115+
}
116+
117+
func test_fishForTransformed() throws {
118+
let p = self.testKit.spawnTestProbe(expecting: String.self)
119+
120+
p.tell("yes-1")
121+
p.tell("yes-2")
122+
p.tell("no-1")
123+
p.tell("no-2")
124+
p.tell("yes-3")
125+
p.tell("yes-end")
126+
127+
let messages = try p.fishFor(String.self, within: .seconds(30)) { message in
128+
if message.contains("yes-end") {
129+
return .catchComplete("\(message)!!!")
130+
} else if message.contains("yes") {
131+
return .catchContinue("\(message)!!!")
132+
} else {
133+
return .ignore
134+
}
135+
}
136+
137+
messages.shouldEqual([
138+
"yes-1!!!",
139+
"yes-2!!!",
140+
"yes-3!!!",
141+
"yes-end!!!",
142+
])
143+
}
144+
145+
func test_fishFor_canThrow() throws {
146+
let p = self.testKit.spawnTestProbe(expecting: String.self)
147+
148+
p.tell("yes-1")
149+
150+
do {
151+
_ = try p.fishForMessages(within: .seconds(30)) { message in
152+
throw TestError("Boom: \(message)")
153+
}
154+
throw self.testKit.fail("Should have thrown")
155+
} catch {
156+
"\(error)".shouldContain("Boom: yes-1")
157+
}
158+
}
159+
89160
// ==== ------------------------------------------------------------------------------------------------------------
90161
// MARK: Actorable
91162

0 commit comments

Comments
 (0)