-
Notifications
You must be signed in to change notification settings - Fork 204
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
[CIS-108] Improve WebSocket tests #268
Conversation
Generated by 🚫 Danger |
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.
🧠 🔥
Tests/Virtual Time/VirtualTime.swift
Outdated
while keepRunning { | ||
|
||
let sortedTimers = scheduledTimers | ||
.map { (time: $0.nextFireTime(currentTime: now), timer: $0) } |
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.
This caused me great confusion.
scheduledTimers
is [Timer]
but Timer
didn't have nextFireTime
func. I went back and forth between commits and realized this Timer
is actually VirtualTime.Timer
Timer
, VirtualTime.Timer
and VirtualTimeTimer
are all too similar. I suggest we find better names.
Tests/Virtual Time/VirtualTime.swift
Outdated
while keepRunning { | ||
|
||
let sortedTimers = scheduledTimers | ||
.map { (time: $0.nextFireTime(currentTime: now), timer: $0) } |
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.
Can we rename time
to nextFireTime
in this tuple? It becomes very confusing reading the code afterwards.
self.now = initialTime | ||
} | ||
|
||
func run(numberOfSeconds: Seconds? = nil) { |
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.
Can you add some documentation to this func/ class? It's hard to grasp what's going on.
Tests/Virtual Time/VirtualTime.swift
Outdated
var timeAdvanceExecutionDelay: TimeInterval = 0.01 | ||
|
||
var scheduledTimers: [Timer] = [] | ||
var now: Seconds |
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.
I suggest renaming this to current
or currentTime
extension VirtualTime.Timer: TimerControl { | ||
func resume() { | ||
isActive = true | ||
} | ||
|
||
func suspend() { | ||
isActive = false | ||
} | ||
} | ||
|
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.
IMHO this should be in the implementation and VirtualTimeTests
should use resume
and suspend
, instead of setting directly
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.
I think there's nothing wrong about setting isActive
directly in the tests. No need to complicate things. This is not public api.
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.
Why would we use internal API in tests? It's safer to be close to real-life usage scenario, in which we'll never access isActive
directly. I also disagree it's complicating, on the contrary I think accessing isActive
is more complicated than just timer.suspend()
or resume()
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.
Bahadir, we're talking about VirtualTimer
which is used solely in tests, never in production. If mutating a variable directly bothers you for some reason, I'll add the functions you want 🤷♂️
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.
That's a fair point, but then why do we have the extensions? If we have functions to mutate these variables, we should only use them.
Even if it's testing something only used for testing, API could be consistent.
I accept your point though, it's up to you, we can keep it.
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.
We have it because both simple and repeating timers are represented by the same object in tests, while in the production, we handle them differently. DispatchQueue.asyncAfter
for simple timers, and DispatchSourceTimer
for repeating timers.
I'll change it so it has the api you want.
/// How many times was `connect()` called | ||
var connectCalledCount = 0 |
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.
Can we group variables and functions? Comment makes it obvious anyway
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.
What do you mean?
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.
ping @b-onc
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.
Putting variables on top, functions after them, and not mixing the order
// MARK: - Functions to simulate behavior | ||
|
||
extension WebSocketProviderMock { |
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.
Can we just use the mark inside main class and not an extension?
reconnectingExpectation.expectedFulfillmentCount = 2 | ||
func test_reconnectionFlow_withoutStopError() { | ||
// Setup (connect) | ||
test_connectionFlow() |
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.
We shouldn't call other tests within a test case, just connect
and simulateconnect
would be fine
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.
I'm not super excited about this either. However, I ended up having the almost exact implementation in two functions. If you feel strongly about this, I'll change it. But it's a worse-of-two-evils kind of decision.
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.
OK Let's keep it then 👍
func test_pingIsSentPeriodically() { | ||
// Setup (connect) | ||
test_connectionFlow() | ||
assert(WebSocket.pingTimeInterval == 25) |
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.
I don't think this makes sense here. We shouldn't assume ping interval will be 25, and write the test with accessing this value (calculated)
} | ||
} | ||
} | ||
extension VirtualTime.TimerControl: TimerControl { } |
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.
Why don't we put it in VirtualTime.swift
file, or directly to implementation?
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.
VirtualTime.swift
is a file independent on StreamChatClient
. You can take it and include to any target you want. VirtualTimers.swift
is where VirtualTime
is used to implement internal protocols StreamChatClient.Timer
and StreamChatClient.TimerControl
. It's good to have them separate.
Tests/Virtual Time/VirtualTime.swift
Outdated
|
||
|
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.
Unnecessary whitespace?
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.
2 minor comments, other than that 🚢
We will use this to inject types into objects.
This allows us to mock timers in tests and run them much faster.
This makes it possible to run timers in tests faster.
2cd9ded
to
29e8ae6
Compare
In this PR:
New
Timer
wrapper type for timers. This allows us to inject custom timers when running tests.It's now possible to inject
Timer.type
toWebSocket
.Introduced
VirtualTime
for running timers in tests without actually waiting. The implementation of therun(_:)
method is a little bit tricky. Please seeVirtualTimeTests
if you want to understand why it's done like this. Also, happy to see your suggestions and improvements.Refactored existing
WebSocket
tests to use virtual time. I didn't refactor the tests forEvent
callbacks because we'll change this part soon and we'll add tests as a part of the refactoring.#no_changelog