Skip to content
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

Merged
merged 8 commits into from
May 15, 2020
Merged

[CIS-108] Improve WebSocket tests #268

merged 8 commits into from
May 15, 2020

Conversation

VojtaStavik
Copy link
Contributor

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 to WebSocket.

  • Introduced VirtualTime for running timers in tests without actually waiting. The implementation of the run(_:) method is a little bit tricky. Please see VirtualTimeTests 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 for Event callbacks because we'll change this part soon and we'll add tests as a part of the refactoring.

#no_changelog

@VojtaStavik VojtaStavik requested review from buh and b-onc May 13, 2020 07:47
@VojtaStavik VojtaStavik changed the title [CIS 108] Improve WebSocket tests [CIS-108] Improve WebSocket tests May 13, 2020
@Stream-SDK-Bot
Copy link
Collaborator

1 Warning
⚠️ Big PR

Generated by 🚫 Danger

Copy link
Contributor

@b-onc b-onc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧠 🔥

Sources/Client/WebSocket/WebSocket.swift Outdated Show resolved Hide resolved
while keepRunning {

let sortedTimers = scheduledTimers
.map { (time: $0.nextFireTime(currentTime: now), timer: $0) }
Copy link
Contributor

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.

while keepRunning {

let sortedTimers = scheduledTimers
.map { (time: $0.nextFireTime(currentTime: now), timer: $0) }
Copy link
Contributor

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) {
Copy link
Contributor

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.

var timeAdvanceExecutionDelay: TimeInterval = 0.01

var scheduledTimers: [Timer] = []
var now: Seconds
Copy link
Contributor

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

Comment on lines 30 to 39
extension VirtualTime.Timer: TimerControl {
func resume() {
isActive = true
}

func suspend() {
isActive = false
}
}

Copy link
Contributor

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

Copy link
Contributor Author

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.

Copy link
Contributor

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()

Copy link
Contributor Author

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 🤷‍♂️

Copy link
Contributor

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.

Copy link
Contributor Author

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.

Comment on lines 29 to 30
/// How many times was `connect()` called
var connectCalledCount = 0
Copy link
Contributor

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ping @b-onc

Copy link
Contributor

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

Comment on lines 46 to 48
// MARK: - Functions to simulate behavior

extension WebSocketProviderMock {
Copy link
Contributor

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()
Copy link
Contributor

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

Copy link
Contributor Author

@VojtaStavik VojtaStavik May 15, 2020

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.

Copy link
Contributor

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)
Copy link
Contributor

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)

@VojtaStavik VojtaStavik requested a review from b-onc May 15, 2020 13:15
}
}
}
extension VirtualTime.TimerControl: TimerControl { }
Copy link
Contributor

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?

Copy link
Contributor Author

@VojtaStavik VojtaStavik May 15, 2020

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.

Comment on lines 133 to 134


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary whitespace?

Copy link
Contributor

@b-onc b-onc left a 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 🚢

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🙏 Feature Request A new feature request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants