-
Notifications
You must be signed in to change notification settings - Fork 0
Test: Add Comprehensive Tests for EventBus #345
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
252 changes: 252 additions & 0 deletions
252
Utils/Tests/UtilsTests/EventBus/EventBusSubscribingTests.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,252 @@ | ||
| // generated by polka.codes | ||
| import Foundation | ||
| import Synchronization | ||
| import Testing | ||
|
|
||
| @testable import Utils | ||
|
|
||
| // TestEvent and AnotherTestEvent are defined in EventBusTests | ||
|
|
||
| // A helper actor to safely check if a handler was called and capture values | ||
| private actor TestHandlerChecker<T> { | ||
| private(set) var callCount = 0 | ||
| private(set) var capturedValues: [T] = [] | ||
|
|
||
| func handlerCalled(with value: T) { | ||
| callCount += 1 | ||
| capturedValues.append(value) | ||
| } | ||
| } | ||
|
|
||
| struct EventBusSubscribingTests { | ||
| @Test func testBasicSubscription() async throws { | ||
| let eventBus = EventBus() | ||
| let handlerChecker = TestHandlerChecker<TestEvent>() | ||
|
|
||
| let testEvent = TestEvent(id: 1, value: "test") | ||
|
|
||
| let token = await eventBus.subscribe(TestEvent.self) { event in | ||
| await handlerChecker.handlerCalled(with: event) | ||
| } | ||
|
|
||
| await eventBus.publish(testEvent) | ||
|
|
||
| // Give some time for the event to be processed | ||
| try await Task.sleep(for: .milliseconds(100)) | ||
|
|
||
| let callCount = await handlerChecker.callCount | ||
| #expect(callCount == 1) | ||
| let capturedEvent = await handlerChecker.capturedValues.first | ||
| #expect(capturedEvent?.id == testEvent.id) | ||
| #expect(capturedEvent?.value == testEvent.value) | ||
|
|
||
| await eventBus.unsubscribe(token: token) | ||
| } | ||
|
|
||
| @Test func testUnsubscribe() async throws { | ||
| let eventBus = EventBus() | ||
| let handlerChecker = TestHandlerChecker<TestEvent>() | ||
|
|
||
| let token = await eventBus.subscribe(TestEvent.self) { event in | ||
| await handlerChecker.handlerCalled(with: event) | ||
| } | ||
|
|
||
| await eventBus.unsubscribe(token: token) | ||
|
|
||
| await eventBus.publish(TestEvent(id: 2, value: "after unsubscribe")) | ||
|
|
||
| // Give some time for the event to be processed | ||
| try await Task.sleep(for: .milliseconds(100)) | ||
|
|
||
| let callCount = await handlerChecker.callCount | ||
| #expect(callCount == 0) | ||
| } | ||
|
|
||
| @Test func testMultipleSubscribers() async throws { | ||
| let eventBus = EventBus() | ||
| let handler1Checker = TestHandlerChecker<TestEvent>() | ||
| let handler2Checker = TestHandlerChecker<TestEvent>() | ||
|
|
||
| let token1 = await eventBus.subscribe(TestEvent.self) { event in | ||
| await handler1Checker.handlerCalled(with: event) | ||
| } | ||
| let token2 = await eventBus.subscribe(TestEvent.self) { event in | ||
| await handler2Checker.handlerCalled(with: event) | ||
| } | ||
|
|
||
| await eventBus.publish(TestEvent(id: 3, value: "multi-subscriber test")) | ||
|
|
||
| try await Task.sleep(for: .milliseconds(100)) | ||
|
|
||
| let callCount1 = await handler1Checker.callCount | ||
| let callCount2 = await handler2Checker.callCount | ||
| #expect(callCount1 == 1) | ||
| #expect(callCount2 == 1) | ||
|
|
||
| await eventBus.unsubscribe(token: token1) | ||
| await eventBus.unsubscribe(token: token2) | ||
| } | ||
|
|
||
| @Test func testSubscribingToDifferentEvents() async throws { | ||
| let eventBus = EventBus() | ||
| let testEventHandler = TestHandlerChecker<TestEvent>() | ||
| let anotherTestEventHandler = TestHandlerChecker<AnotherTestEvent>() | ||
|
|
||
| let token1 = await eventBus.subscribe(TestEvent.self) { event in | ||
| await testEventHandler.handlerCalled(with: event) | ||
| } | ||
| let token2 = await eventBus.subscribe(AnotherTestEvent.self) { event in | ||
| await anotherTestEventHandler.handlerCalled(with: event) | ||
| } | ||
|
|
||
| await eventBus.publish(TestEvent(id: 4, value: "first event")) | ||
| await eventBus.publish(AnotherTestEvent(name: "second event")) | ||
|
|
||
| try await Task.sleep(for: .milliseconds(100)) | ||
|
|
||
| let testEventCallCount = await testEventHandler.callCount | ||
| let anotherEventCallCount = await anotherTestEventHandler.callCount | ||
| #expect(testEventCallCount == 1) | ||
| #expect(anotherEventCallCount == 1) | ||
|
|
||
| await eventBus.unsubscribe(token: token1) | ||
| await eventBus.unsubscribe(token: token2) | ||
| } | ||
|
|
||
| @Test func testUnsubscribeOneOfMany() async throws { | ||
| let eventBus = EventBus() | ||
| let handler1Checker = TestHandlerChecker<TestEvent>() | ||
| let handler2Checker = TestHandlerChecker<TestEvent>() | ||
|
|
||
| let token1 = await eventBus.subscribe(TestEvent.self) { event in | ||
| await handler1Checker.handlerCalled(with: event) | ||
| } | ||
| let token2 = await eventBus.subscribe(TestEvent.self) { event in | ||
| await handler2Checker.handlerCalled(with: event) | ||
| } | ||
|
|
||
| await eventBus.unsubscribe(token: token1) | ||
|
|
||
| await eventBus.publish(TestEvent(id: 5, value: "after one unsubscribe")) | ||
|
|
||
| try await Task.sleep(for: .milliseconds(100)) | ||
|
|
||
| let callCount1 = await handler1Checker.callCount | ||
| let callCount2 = await handler2Checker.callCount | ||
| #expect(callCount1 == 0) | ||
| #expect(callCount2 == 1) | ||
|
|
||
| await eventBus.unsubscribe(token: token2) | ||
| } | ||
|
|
||
| // MARK: - Middleware Tests | ||
|
|
||
| private struct EventModifyingMiddleware: MiddlewareProtocol { | ||
| func handle<T: Sendable>(_ event: T, next: @escaping MiddlewareHandler<T>) async throws { | ||
| if let testEvent = event as? TestEvent { | ||
| let modifiedEvent = TestEvent(id: testEvent.id, value: "modified") | ||
| try await next(modifiedEvent as! T) | ||
| } else { | ||
| try await next(event) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @Test func testEventMiddleware() async throws { | ||
| let eventBus = EventBus(eventMiddleware: Middleware(EventModifyingMiddleware())) | ||
| let handlerChecker = TestHandlerChecker<TestEvent>() | ||
|
|
||
| let token = await eventBus.subscribe(TestEvent.self) { event in | ||
| await handlerChecker.handlerCalled(with: event) | ||
| } | ||
|
|
||
| await eventBus.publish(TestEvent(id: 6, value: "original")) | ||
|
|
||
| try await Task.sleep(for: .milliseconds(100)) | ||
|
|
||
| let capturedEvent = await handlerChecker.capturedValues.first | ||
| #expect(capturedEvent?.value == "modified") | ||
|
|
||
| await eventBus.unsubscribe(token: token) | ||
| } | ||
|
|
||
| private actor HandlerCountingMiddleware: MiddlewareProtocol { | ||
| private(set) var counter = 0 | ||
| func handle<T: Sendable>(_ event: T, next: @escaping MiddlewareHandler<T>) async throws { | ||
| counter += 1 | ||
| try await next(event) | ||
| } | ||
| } | ||
|
|
||
| @Test func testHandlerMiddleware() async throws { | ||
| let countingMiddleware = HandlerCountingMiddleware() | ||
| let eventBus = EventBus(handlerMiddleware: Middleware(countingMiddleware)) | ||
| let handler1Checker = TestHandlerChecker<TestEvent>() | ||
| let handler2Checker = TestHandlerChecker<TestEvent>() | ||
|
|
||
| let token1 = await eventBus.subscribe(TestEvent.self) { event in | ||
| await handler1Checker.handlerCalled(with: event) | ||
| } | ||
| let token2 = await eventBus.subscribe(TestEvent.self) { event in | ||
| await handler2Checker.handlerCalled(with: event) | ||
| } | ||
|
|
||
| await eventBus.publish(TestEvent(id: 7, value: "handler middleware test")) | ||
|
|
||
| try await Task.sleep(for: .milliseconds(100)) | ||
|
|
||
| let count = await countingMiddleware.counter | ||
| #expect(count == 2) | ||
| let callCount1 = await handler1Checker.callCount | ||
| let callCount2 = await handler2Checker.callCount | ||
| #expect(callCount1 == 1) | ||
| #expect(callCount2 == 1) | ||
|
|
||
| await eventBus.unsubscribe(token: token1) | ||
| await eventBus.unsubscribe(token: token2) | ||
| } | ||
|
|
||
| // MARK: - EventSubscriptions Helper Tests | ||
|
|
||
| @Test func testEventSubscriptionsDeinit() async throws { | ||
| let eventBus = EventBus() | ||
| let handlerChecker = TestHandlerChecker<TestEvent>() | ||
|
|
||
| var subscriptions: EventSubscriptions? = EventSubscriptions(eventBus: eventBus) | ||
| await subscriptions!.subscribe(TestEvent.self, id: "test") { event in | ||
| await handlerChecker.handlerCalled(with: event) | ||
| } | ||
|
|
||
| // Deinitialize EventSubscriptions | ||
| subscriptions = nil | ||
|
|
||
| // Allow time for deinit task to run | ||
| try await Task.sleep(for: .milliseconds(100)) | ||
|
|
||
| await eventBus.publish(TestEvent(id: 8, value: "after deinit")) | ||
|
|
||
| try await Task.sleep(for: .milliseconds(100)) | ||
|
|
||
| let callCount = await handlerChecker.callCount | ||
| #expect(callCount == 0) | ||
| } | ||
|
|
||
| @Test func testEventSubscriptionsExplicitUnsubscribe() async throws { | ||
| let eventBus = EventBus() | ||
| let subscriptions = EventSubscriptions(eventBus: eventBus) | ||
| let handlerChecker = TestHandlerChecker<TestEvent>() | ||
|
|
||
| let token = await subscriptions.subscribe(TestEvent.self, id: "test") { event in | ||
| await handlerChecker.handlerCalled(with: event) | ||
| } | ||
|
|
||
| await subscriptions.unsubscribe(token: token) | ||
|
|
||
| await eventBus.publish(TestEvent(id: 9, value: "after explicit unsubscribe")) | ||
|
|
||
| try await Task.sleep(for: .milliseconds(100)) | ||
|
|
||
| let callCount = await handlerChecker.callCount | ||
| #expect(callCount == 0) | ||
| } | ||
| } | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using
Task.sleepwith a fixed duration to wait for an asynchronous operation to complete makes the test both slow and unreliable. If the operation takes longer than the sleep duration, the test will fail incorrectly (flakiness). A better approach is to actively wait for the desired condition to become true.This issue is present in all tests in this file. Please replace the
Task.sleepcalls with a more deterministic mechanism.For example, you could introduce a polling helper function:
And then use it in the test like this, removing the
Task.sleep: