Delay in unit test - swift

So I have a unit test to test if clinics are being updated every 10 seconds. After 5 seconds, I clear all of the clinics. Then set an expectation that times out after 9 seconds to make sure clinics were updated. Here is my code:
func testRefresh() {
let expec = expectation(description: "Clinics timer expectation")
let expec2 = expectation(description: "Clinics timer expectation2")
expec2.isInverted = true
let dispatchGroup = DispatchGroup(count: 5)
dataStore.load()
wait(for: [expec2], timeout: 5.0) // This is what I am asking about
self.dataStore.clinicsSignal.fire([])
dataStore.clinicsSignal.subscribeOnce(with: dispatchGroup) {
print("clinics signal = \($0)")
expec.fulfill()
}
wait(for: [expec], timeout: 9.0)
XCTAssertFalse(self.dataStore.clinics.isEmpty)
}
I want to have that delay for 5 seconds. Using an inverted expectation the way I did is the only way I could find to make it work. I just think using an inverted expectation is bad practice.
If I use sleep(5) it stops the whole program for 5 seconds. I have also tried a solution using DispatchQueue.main.asyncAfter like outlined here but to no avail.

I have two suggestions to use together:
Use a spy test double to make sure that the service your data store uses to refresh the clinics is called twice
Inject the refresh interval to make the tests faster
Spy test double
Testing the side effect of the data loading, that it hits the service, could be a way to simplify your test.
Instead of using different expectations and exercising the system under test in a way that might not be what happens at runtime (the dataStore.clinicsSignal.fire([])) you can just count how many times the service is hit, and assert the value is 2.
Inject refresh interval
The approach I would recommend is to inject the time setting for how frequently the clinics should be updated in the class, and then set a low value in the tests.
After all, I'm guessing what you are interested in is that the update code runs as expected, not every 10 seconds. That is, it should update at the frequency you set.
You could do this by having the value as a default in the init of your data store, and then override it in the tests.
The reason I'm suggesting to use a shorter refresh interval is that in the context of unit testing, the faster they run the better it is. You want the feedback loop to be as fast as possible.
Putting it all together, something more or less like this
protocol ClinicsService {
func loadClinics() -> SignalProducer<[Clinics], ClinicsError>
}
class DataSource {
init(clinicsService: ClinicsService, refreshInterval: TimeInterval = 5) { ... }
}
// in the tests
class ClinicsServiceSpy: ClinicsService {
private(var) callsCount: Int = 0
func loadClinics() -> SignalProducer<[Clinics], ClinicsError> {
callsCount += 1
// return some fake data
}
}
func testRefresh() {
let clinicsServiceSpy = ClinicsServiceSpy()
let dataStore = DataStore(clinicsService: clinicsServiceSpy, refreshInterval: 0.05)
// This is an async expectation to make sure the call count is the one you expect
_ = expectation(
for: NSPredicate(
block: { input, _ -> Bool in
guard let spy = input as? ClinicsServiceSpy else { return false }
return spy.callsCount == 2
),
evaluatedWith: clinicsServiceSpy,
handler: .none
)
dataStore.loadData()
waitForExpectations(timeout: .2, handler: nil)
}
If you also used Nimble to have a more refined expectation API your test could look like this:
func testRefresh() {
let clinicsServiceSpy = ClinicsServiceSpy()
let dataStore = DataStore(clinicsService: clinicsServiceSpy, refreshInterval: 0.05)
dataStore.loadData()
expect(clinicsServiceSpy.callsCount).toEventually(equal(2))
}
The tradeoff you make in this approach is to make the test more straightforward by writing a bit more code. Whether it's a good tradeoff is up to you do decide.
I like working in this way because it keeps each component in my system free from implicit dependencies and the test I end up writing are easy to read and work as a living documentation for the software.
Let me know what you think.

Related

Multiple detached tasks are not executed at the same time

class ViewModel: ObservableObject { }
struct ContentView: View {
#StateObject private var model = ViewModel()
var body: some View {
Button("Authenticate", action: doWork)
}
func doWork() {
Task.detached {
for i in 1...10_000 {
print("In Task 1: \(i)")
}
}
Task.detached {
for i in 1...10_000 {
print("In Task 2: \(i)")
}
}
}
}
This is the code described in https://www.hackingwithswift.com/quick-start/concurrency/whats-the-difference-between-a-task-and-a-detached-task.
Since the Tasks in doWork are detached, I expect them to be executed at the same time.
The above article also says so.
However, when I run this, Task2 is executed after Task1.
Am I wrong?
By putting the items into detached tasks, you are letting the operating system decide when to schedule them. Apparently, for one of the times you looked, the system decided to schedule them one after the other. There's nothing to say that the next time you look they won't be scheduled in parallel, or so that the second one runs before the first one.
The point is that you don't have any control over it. And the OS may make a different decision tomorrow than it does today. You've ceded that control to the OS by putting the work in Tasks.
Multiple detached tasks do run concurrently. Consider this example, where I perform a computationally intensive operation 20 times, each in its own task:
import SwiftUI
import os.log
private let log = OSLog(subsystem: "Detached tasks", category: .pointsOfInterest)
struct ContentView: View {
var body: some View {
Button("Do work", action: doWork)
}
func doWork() {
os_signpost(.event, log: log, name: #function)
for i in 0 ..< 20 {
Task.detached {
let id = OSSignpostID(log: log)
os_signpost(.begin, log: log, name: #function, signpostID: id, "start %d", i)
let value = calculatePi(decimalPlaces: 9)
print(value)
os_signpost(.end, log: log, name: #function, signpostID: id, "done")
}
}
}
// deliberately inefficient calculation of pi using Leibniz series
func calculatePi(decimalPlaces: Int = 9) -> Double {
let threshold = pow(0.1, Double(decimalPlaces))
var isPositive = true
var denominator: Double = 1
var value: Double = 0
var increment: Double
repeat {
increment = 4 / denominator
if isPositive {
value += increment
} else {
value -= increment
}
isPositive.toggle()
denominator += 2
} while increment >= threshold
return value
}
}
We can profile that task, using the “Points of Interest” tool. On the simulator that yields:
Note, that is artificially constraining the cooperative thread pool to two tasks at a time.
On an iPhone 12 Pro Max:
That runs six at a time.
And on an Intel 2018 MacBook Pro:
So, bottom line, it does run concurrently, constrained based upon the nature of the hardware on which you run it (with the exception of the simulator, which artificially constrains it even further).
FWIW, the 10,000 print statements is not a representative example because:
Too many synchronizations: The print statements are synchronized across threads which can skew the results. To test concurrency, you ideally want as few synchronizations taking place as possible.
Not enough work on each thread: A for loop of only 10,000 iterations is not enough work to properly manifest concurrency. It is quite easy that the first task could finish before the second even starts. And even if it does start interleaving at some point, you might see a couple thousand on one task, then a couple thousand on the next, etc. Do not expect a rapid interleaving of the print statements.
For these reasons, I replaced that for loop with a calculation of pi to 9 decimal places. The idea is to have some calculation/process that is sufficiently intensive to warrant concurrency and manifest the behavior we are looking for.
Perhaps needless to say, if we experience serial behavior if we:
move this calculation to an actor; and
launch with Task { ... } rather than detached task.
(Note, the horizontal time scale as been compressed to fit the twenty tasks on screen at the same time.)

Unit Testing asynchronous functions called by various didSets in Swift

I'm trying to unit test a property in a ViewModel (in Swift) that is dependent on a series of other properties being set, and having trouble doing so.
I have a viewModel which includes a timeLength, a timePeriod, and a listOfObjects that fits my time period.
These values depend on the previous one,
timeLength and timePeriod are both stored in the user's profile, and my code follows this flow:
Search for a timeLength in the profile.
If none is found, allow the user to select a timeLength
Once the timeLength is set, a didSet triggers a flow that searches for a matching timePeriod, (in a mock repository for time periods) or creates one if one doesn't exist.
Once the timePeriod exists, a didSet triggers a flow that searches a database to compile a listOfObjects that match that timePeriod (using a separate mockRepository for objects).
I'm trying to create a unit test that checks the list of objects once that code flow is completed, but every time I do so, the XCTAssertEqual method completes before the code flow is finished. I tried using XCTestExpectation, as described in Hacking with Swift, but I don't have a specific asynchronous method to call, since this is all triggered by a series of didSet calls.
I could create a timePeriod in the repository, which would trigger the listOfObjects to be set in the ViewModel, but then I'd be missing out on the full flow of this. Is there a way to just have the test complete after a few seconds?
(Or is this a bad test since I'm relying on the system to do multiple things at once in order to pass the test?)
Here's the specific test (right now it won't do anything since there's nothing to fulfill the asynchronous wait)
func testSeasonObjectsVM_loadData() {
// given
let exp = expectation(description: "loading Data")
sut.timeLength = .quarter
waitForExpectations(timeout: 20)
// when
let count = sut.listOfObjects.count
// assert
XCTAssertEqual(count, 2)
}
You can fire a notification from the final place that you know the process / series of didSets has been completed like following.
public extension Notification.Name {
static let UserProfileViewModelListOfObjectsLoaded =
Notification.Name(rawValue: "UserProfileViewModelListOfObjectsLoaded")
}
// Your viewModel's final didSet
var listOfObjects: [Any] {
didSet {
NotificationCenter.default.post(name: .UserProfileViewModelListOfObjectsLoaded, object: nil)
}
}
// In your test
self.expectation(forNotification: .UserProfileViewModelListOfObjectsLoaded, object: nil) { (notification) -> Bool in
return (sut.listOfObjects.count == 2)
}

DispatchWorkItem How to cancel recursive process?

I have a class Complicated, where (it's not a real code):
class BeReadyInSomeTime {
var someData: SomeData
var whenDone: () -> Void
var isDone: Bool = false
var highRes: [LongCountedStuff] = []
init(data:SomeData, whenDone: #escaping () - >Void) {
self.someData = someData
self.whenDone = whenDone
... prepare `highRes` in background...
{ makeHighRes() }
... and when done set `isDone` to `true`, fire `whenDone()`
}
func reset(data:SomeData) {
self.someData = someData
self.isDone = false
self.highRes = []
... forget **immediately** about job from init or reset, start again
{ makeHighRes() }
... and when done set `isDone` to `true`, fire `whenDone()`
}
var highResolution:AnotherType {
if isDone {
return AnotherType(from: highRes)
} else {
return AnotherType(from: someData)
}
}
func makeHighRes() {
var result = [LongCountedStuff]
// prepare data, fast
let some intermediateResult = almost ()
self.highRes = result
}
func almost() -> [LongCountedStuff] {
if isNice {
return countStuff(self.someData)
} else {
return []
}
func countStuff(stuff:[LongCountedStuff], deep:Int = 0) -> [LongCountedSuff] {
if deep == deep enough {
return stuff
} else {
let newStuff = stuff.work
count(newStuff, deep: deep+1)
}
}
Making highRes array is a recurrent function which calls itself many times and sometimes it takes seconds, but I need feedback as fast as possible (and it will be one of someData elements, so I'm safe). As far I know, I can only 'flag' DispatchWorkItem that's cancelled. If I deliver new data by reset few times per second (form mouse drag) whole block is counted in background as many times as data was delivered. How to deal with this kind of problem? To really break counting highRes?
If you have a routine that is constantly calling another framework and you want to stop it at the end of one iteration and before it starts the next iteration, then wrapping this in an Operation and checking isCancelled is a good pattern. (You can also use GCD and DispatchWorkItem and use its isCancelled, too, but I find operations do this more elegantly.)
But if you’re saying you not only want to cancel your loop, but also hope to stop the consuming call within that framework, then, no, you can’t do that (unless the framework provides some cancelation mechanism of its own). But there is no preemptive cancellation. You can’t just stop a time consuming calculation unless you add checks inside that calculation to check to see if it has been canceled.
I’d also ask whether the recursive pattern is right here. Do you really need the results of one calculation in order to start the next? If so, then a recursive (or iterative) pattern is fine. But if the recursive operation is just to pass the next unit of work, then a non-recursive pattern might be better, because it opens up the possibility of doing calculations in parallel.
For example, you might create a concurrent queue with a maxConcurrencyCount of some reasonable value (e.g. 4 or 6). Then wrap each individual processing task in its own Operation subclass and have each check its respective isCancelled. Then you can just add all the operations up front, and let the queue handle it from there. And when you want to stop them, you can tell the queue to cancelAllOperations. It’s a relative simple pattern, allows you to do calculations in parallel, and is cancelable. But this obviously only works if a given operations is not strictly dependent upon the results of the prior operation(s).

XCTest - Unable to understand / implement expectations in unit tests (to test aysnc code)

(NOTE - I'm developing for macOS, so please ... iOS-specific advice won't help me)
What I'm trying to do:
I have an app component that performs a short task on a background thread, and then, if certain conditions are met, asynchronously sends out a notification on the main thread.
NOTE - I am not using NSNotification in my app code. I am using my own custom notification mechanism. So, any solution related to NSNotification is not applicable to me.
I'm writing a unit test for the above mentioned app component, and simply want to check if that notification was indeed sent or not. My test has to be able to wait a second or so to give the notification time to reach its subscriber/observer, before performing an assertion.
I want to be able to test both possible cases in my tests: Both are normal scenarios.
Notification was sent.
Notification was not sent.
After hours of reading several docs and code samples, I don't understand how to achieve this with expectations.
I just want to wait one second in my test. Is it really this complicated ?
sleep() doesn't work
DispatchQueue.main.asyncAfter(time) doesn't work
Timer doesn't work
Here's the app component that needs to be tested, and its unit test:
In the below code, where do I put expectation.fulfill() ???
class ComponentBeingTested {
func methodBeingTested() {
doSomeWork()
if certainConditionsAreMet {
DispatchQueue.main.async {sendOutNotification()}
}
}
}
...
class UnitTestForComponentBeingTested: XCTestCase {
let objectBeingTested = ComponentBeingTested()
func testMethodBeingTested() {
let expectation = self.expectation(description: "Notification was sent")
// Call the code being tested
objectBeingTested.methodBeingTested()
// How do I do this with expectations ??? Where does expectation.fulfill() go ?
waitForOneSecond()
XCTAssertTrue(notificationSent) // Assume the value of notificationSent is available
}
}
Here is an approach
func testMethodBeingTested() {
// create expectation
let expectation = self.expectation(description: "Notification was sent")
// set expectation condition
var notificationSent = false
let observer = NotificationCenter.default
.addObserver(forName: _Your_Notification_Name, object: nil, queue: nil) { _ in
notificationSent = true
expectation.fulfill()
}
// Call the code being tested
objectBeingTested.methodBeingTested()
// wait for expectation
self.wait(for: [expectation], timeout: 5)
XCTAssertTrue(notificationSent)
}
Check out XCTNSNotificationExpectation, which becomes fulfilled when a matching notification is posted. Different initializers are available, depending on how restrictive you want to be on the fulfilment of the expectation.
To check that the notification is not sent, set isInverted to true on the expectation object.
Then just add a call to waitForExpectations(timeout:handler:) at the end of your test.
Ok, after a lot of trial and error, this works great for me:
Description: I basically created a helper function in my test case class that contains all the boilerplate expectation/wait code. It does the following:
1 - Creates an expectation (i.e. XCTestExpectation) as a formality.
2 - Calls my (arbitrary) test case assertion code (passed in as a closure) on some global queue thread after the intended delay period. Once this assertion code has completed, the expectation is fulfilled (again, a formality).
3 - Waits on the expectation by calling XCTestCase.wait(timeout). This ensures that the main thread / run loop is kept alive while my assertion code completes on this other thread.
Then, in my test case, I simply invoke that helper function, providing it with a wait period and some code to execute (i.e. my assertions).
This way, I have a simple and expressive reusable function that hides all the excessive ugliness of expectations which I never thought necessary in the first place.
I can put this helper in a base class like MyAppTestCase: XCTestCase, so that it is available to all my test case classes.
NOTE - This solution can be enhanced and made even more generic/reusable, but as of now, this is quite sufficient for the purposes of the originally posted problem.
Solution:
class ComponentBeingTested {
func methodBeingTested() {
doSomeWork()
if certainConditionsAreMet {
DispatchQueue.main.async {sendOutNotification()}
}
}
}
...
class UnitTestForComponentBeingTested: XCTestCase {
let objectBeingTested = ComponentBeingTested()
// Helper function that uses expectation/wait to execute arbitrary
// test code (passed in as a closure) after some delay period.
func executeAfter(_ timeSeconds: Double, _ work: (#escaping () -> Void)) {
let theExpectation = expectation(description: "some expectation")
// Execute work() after timeSeconds seconds
DispatchQueue.global(qos: .userInteractive).asyncAfter(deadline: .now() + timeSeconds) {
// The call to work() will execute my test assertions
work()
// As a formality, fulfill the expectation
theExpectation.fulfill()
}
// Wait for (timeSeconds + 1) seconds to give the work() call
// some time to perform the assertions
wait(for: [theExpectation], timeout: timeSeconds + 1)
}
func testMethodBeingTested() {
// Call the code being tested
objectBeingTested.methodBeingTested()
// Call the helper function above, to do the waiting before executing
// the assertions
executeAfter(0.5) {
// Assume the value of notificationSent is computed elsewhere
// and is available to assert at this point
XCTAssertTrue(notificationSent)
}
}
}

How to test a function that gets into the main thread in Swift with RxSwift and XCTest?

I came across this problem when testing my View:
In my ViewModel I call to an asynchronous operation and when the response arrives, I use a PublishSubject to produce a change in my View. In my View, I call DispatchQueue.main.async in order to hide or show a button.
ViewModel
let refreshButtons = PublishSubject<Bool>(true)
refreshButtons.onNext(true)
View
model.refreshButtons.asObservable()
.subscribe(onNext: {
[unowned self] success in
self.updateButtons(success)
})
.addDisposableTo(disposable)
private func updateButtons(_ show:Bool) {
DispatchQueue.main.async{
button.isHidden = !show
}
}
Now I don't know how to unit test that refreshButtons.onNext(true) will hide or show my button.
The solutions I can think of are:
Overriding the method and having an async expectation, but for that I need to make the method public, what I don't want, or
Dispatching the main queue in my ViewModel and not in the view, what it sounds odd to me, but might me ok.
How can I solve this?
Thank you in advance.
You could use an async expectation based on a predicate in your unit test to wait an see if the button is not hidden anymore.
func testButtonIsHidden() {
// Setup your objects
let view = ...
let viewModel = ...
// Define an NSPredicate to test your expectation
let predicate = NSPredicate(block: { input, _ in
guard let _view = input as? MyView else { return false }
return _view.button.isHidden == true
})
// Create an expectation that will periodically evaluate the predicate
// to decided whether it's fulfilled or not
_ = expectation(for: predicate, evaluatedWith: view, handler: .none)
// Call the method that should generate the behaviour you are expecting.
viewModel.methodThatShouldResultInButtonBeingHidden()
// Wait for the
waitForExpectationsWithTimeout(1) { error in
if let error = error {
XCTFail("waitForExpectationsWithTimeout errored: \(error)")
}
}
}
Something worth noting is that the value you pass to the NSPredicate should be a class. That is because classes are passed by reference, so value inside the predicate block will be the same as the one touched by your view model. If you were to pass a struct or enum though, which are passed by copy, the predicate block would receive a copy of the value as it is at the time of running the setup code, and it will always fail.
If instead you prefer to use UI tests as suggested by #Randall Wang in his answer, then this post might be useful for you: "How to test UI changes in Xcode 7". Full disclosure, I wrote that post.
First of all, You don't need test private method
If you want to test if the button is hidden or not,try UI testing
here is the WWDC of UI testing.
https://developer.apple.com/videos/play/wwdc2015/406/