I have this unit test:
func testState() {
searchController.filter.query = "Missouri"
searchController.resultsSignal.subscribePast(with: self) { sections in
if sections.count < 1 { return }
// Want to test that code at least gets to here at some point
...
}
}
And I want to make sure that I at least get past the line if sections.count < 1 { return } at some point.
Since it is getting ran when a signal is fired, I don't care if there is a different signal that gets fired at some point, but I do want to make sure that sections.count > 0 at some point in the test.
Is there a way to do this? I was thinking of using a boolean and initializing it to false then setting it to true if sections.count was ever more than one, and asserting that the value was true, but this doesn't work unless I do a delay since I am using signals. Thanks.
You can use an XCTestExpectation and call .fulfill after sections.count to notify the test that an asynchronous test has succeeded.
func testState() {
let expectation = XCTestExpectation(description: "Should execute")
searchController.filter.query = "Missouri"
searchController.resultsSignal.subscribePast(with: self) { sections in
if sections.count < 1 { return }
// Want to test that code at least gets to here at some point
expectation.fulfill()
...
}
wait(for: [expectation], timeout: 10) // Will fail if .fulfill does not get called within ten seconds
}
Related
I've written a method loadCountries() that performs asynchronous file loading upon calling and then notifies its output that the work is done. I'm trying to measure this method's performance in my tests but I can't figure out how to handle asynchronous operations without callbacks with measure block.
I found out there are startMeasuring() and stopMeasuring() methods that allow to manually set entry and end points for the test. I tried to call the latter in my output mock:
let output = InteractorOutputMock()
output.onDisplay = { _ in
self.stopMeasuring()
}
let interactor = PremiumLocationsChooserInteractor()
interactor.output = output
measureMetrics([.wallClockTime], automaticallyStartMeasuring: false) {
self.startMeasuring()
interactor.loadCountries()
}
But the code still finishes in 0 secs. How should I approach this issue?
UPDATE:
I tried using expectations as well, but ran into problem. I can neither call it inside the measure block nor outside of it. The former looks like the following and causes the code to wait in the first iteration of measure:
let outputCalledExpectation = XCTestExpectation(description: "Output hasn't been called")
outputCalledExpectation.expectedFulfillmentCount = 10 // need to fullfill it 10 times since this is how much measure block is called
let output = InteractorOutputMock()
output.onDisplay = { _ in
self.stopMeasuring() // called only once
outputCalledExpectation.fulfill()
}
let interactor = PremiumLocationsChooserInteractor()
interactor.output = output
measureMetrics([.wallClockTime], automaticallyStartMeasuring: false) {
startMeasuring()
interactor.loadCountries()
wait(for: [outputCalledExpectation], timeout: 5.0) // then stack here
}
And if I try to move the wait method outside of the block, I get exception -stopMeasuring is only supported from a block passed to -measure...Block:
measureMetrics([.wallClockTime], automaticallyStartMeasuring: false) {
startMeasuring()
interactor.loadCountries()
}
wait(for: [outputCalledExpectation], timeout: 5.0) // get exception here
I use this extension for measuring, works like a charm for me.
extension XCTestCase {
func measureAsync(
timeout: TimeInterval = 2.0,
for block: #escaping () async throws -> Void,
file: StaticString = #file,
line: UInt = #line
) {
measureMetrics(
[.wallClockTime],
automaticallyStartMeasuring: true
) {
let expectation = expectation(description: "finished")
Task { #MainActor in
do {
try await block()
expectation.fulfill()
} catch {
XCTFail(error.localizedDescription, file: file, line: line)
expectation.fulfill()
}
}
wait(for: [expectation], timeout: timeout)
}
}
}
I am trying to update a label to indicate status, but it is not being refreshed/shown as expected
func setStatusText(status: String) {
statusText.stringValue = status;
}
func doSomething() {
setStatusText(status: "Activating license"); // This is never shown
var rc = PerformAction()
if (rc == 0) {
setStatusText(status: "\(action) succeeded") // Correctly displayed
} else {
setStatusText(status: "\(action) failed") // Correctly displayed
}
The status update before the action is never being shown, but the status set right after the action is correctly displayed. What do I do to be sure the first status is shown?
Edited: I tried something else to try to isolate it. I added a button press, which will set text, sleep, and then set text again. I never see "first" in the status.
#IBAction func TestSettingText(_ sender: Any) {
self.statusText.stringValue = "first";
sleep(15)
self.statusText.stringValue = "second";
}
I don't know what exactly PerformAction does but it seems like it finishes quickly so you just can't see the Activating license status. It is there but only for a very brief moment and instantly changed to the next status
With return it is possible to exit the scope of the current function but is it also possible to exit the scope of the outer function that is calling the inner function?
func innerFunction() {
guard 1 == 2 else {
// exit scope of innerFunction and outerFunction
}
}
func outerFunction() {
innerFunction()
print("should be unreachable")
}
There could be one approach using a return value of the inner function that we can check for:
func innerFunction() -> Bool {
guard 1 == 2 else {
return false
}
return true
}
func outerFunction() {
guard innerFunction() else {
return
}
print("should be unreachable")
}
The problem with this approach is that it can clutter your code pretty quickly if the functions become more complicated and you have to use them over and over again.
Consider applying this approach with XCTest. It would look like this:
func testFoobar() {
guard let unwrappedObject = helperFunction() else {
XCTFail("A failure message that can change with each helperFunction call")
return
}
print("should be unreachable when helperFunction already failed")
}
I'd like to have something similar to this:
func testFoobar() {
let unwrappedObject = helperFunction()
print("should be unreachable when helperFunction already failed")
}
This is basically what Swift's error handling does:
func outer() throws
{
try inner()
print("Unreachable")
}
struct DoNotContinue : Error {}
func inner() throws
{
throw DoNotContinue()
}
do { try outer() }
catch _ { print("Skipped the unreachable") }
Note, of course, that the caller still has control over this: it could catch the thrown error itself instead of just exiting.
problem with this approach is that it can clutter your code
There's a much more serious problem with allowing callees to directly exit their callers, and that is that the flow of control very quickly becomes incomprehensible. Imagine that you have a couple of layers of this. Reading the top-level function, you no longer have any clear idea what can happen. You must yourself recurse into every callee's callee to make sure that the original code will continue on its course.
I've recreated the example from here: http://www.mokacoding.com/blog/testing-callbacks-in-swift-with-xctest/.
I want to test for a timeout using waitForExpectations(). This should mimic a long running process that has timed out. To do this, I've set a sleep() command in the called function that is longer than the timeout in waitForExpectations().
However, the sleep() doesn't have any effect. The test always passes. I've tried putting sleep() before completion(true) as well but that doesn't change the outcome (i.e., passed test).
Any ideas what I'm doing run to trigger a test failure on timeout?
class SomeService {
func doSomethingAsync(completion: (_ success: Bool) -> ()) {
completion(true)
sleep(5)
}
}
In test class
let service = SomeService()
service.doSomethingAsync { (success) in
XCTAssertTrue(success, "assert is true")
expect.fulfill()
}
waitForExpectations(timeout: 3) { (error) in
if let error = error {
XCTFail("timeout errored: \(error)")
}
}
Your test passes because you are calling completion before sleep, so your expectation is being fulfilled almost immediately - before you wait for 5 seconds; while the completion block is executed asynchronously, it is likely going to finish in under a second.
If you call sleep insidecompletion then your test will fail as expected. However, your test may crash if the test is no longer running when expect.fulfill() is called since expect may no longer exist by the time it is executed, as it may have been cleaned up as soon as the test fails (about 2 seconds before the expectation will be fulfilled).
class SomeService {
func doSomethingAsync(completion: (_ success: Bool) -> ()) {
DispatchQueue.main.async {
completion(true)
}
}
}
Test:
let service = SomeService()
service.doSomethingAsync { (success) in
XCTAssertTrue(success, "assert is true")
sleep(5)
expect.fulfill()
}
waitForExpectations(timeout: 3) { (error) in
if let error = error {
XCTFail("timeout errored: \(error)")
}
}
I'm beyond baffled how to accomplish this.
I want to call a function (func1) that will call func2 which does some Parse queries and submits them to the database.
Once all those queries have completed and func2 is completely finished running I want to run func3 which will do similar tasks.
I want to then update my tableView once func3 has completely finished running but I'm having no luck working with GCD.
My issue is when I call func1() then wait for the group to finish the tableview reload data function is executed before func3 is executed.
What would be the best way to solve this?
let group = DispatchGroup()
let queue1 = DispatchQueue()
let queue2 = DispatchQueue()
let queue3 = DispatchQueue()
func1(){
queue1.async(group: group){
for i in 0...10 {
func2(i)
}
}
group.notify(queue: queue2){
func3()
}
}
func2(i: Int){
queue2.async(group: group){
// Perform query for value i
PFQuery.findObjectsInBackground {}
}
}
func3(){
queue3.async(group: group){
PFQuery.findObjectsInBackground {}
}
}
func1()
group.notify(queue: queue4){
tableView.reloadData()
}
To simplify:
func1() calls func2() several times in a for loop.
all of the func2() calls must finish before func3() begins
func3() must finish before tableview can reload.
Should you rather create a single method which is like composition for all asynchronous calculations. That makes it easy to have your control. And yes, you should call the notify after your last call only.
func func1(){
queue1.async(group: group){
print("Some work in background queue 1")
}
}
func func2(){
queue2.async(group: group){
print("Some work in background queue 2")
}
}
func func3(){
group.notify(queue: queue3) {
print("Some work in background queue 3")
}
}
func doCompositeOperations() {
func1()
func2()
DispatchQueue.main.async(group: group) {
print("Reloading after all")
}
func3()
}
doCompositeOperations()
I think func1, func2 func3 ... also perform some asynchronous operation in the queue of its own and returns the result in completion handler. I rather prefere enter and leave variant of dispatch group in cases like such where you can have finer control.
Here's my first stab at this, given this information:
func1() calls func2() several times in a for loop.
all of the func2() calls must finish before func3() begins
func3() must finish before tableview can reload.
func1() {
let group = DispatchGroup()
for element in elements {
group.enter()
func2(element, completion: { group.leave() })
}
group.await()
func3(completion: {tableview.reload() })
}
func func1() {
// Call func 2 and give it a block of work to execute when it is done
func2(completionHandler: {
// call func3 and give it a block of work to do when it is done
func3(completionHandler: {
// call back to the main thread to update the table
update_my_table()
}
})
}
func func2(completionHandler: () -> void) {
PFQuery.findObjectsInBackground()
... other work that must be done before func3 ...
// execute work that should be done when func2 completes
completionHandler()
}
func func3(completionHandler: () -> void) {
... do some stuff to prepare table data ...
// execute work that must be done when func 3 completes
completionHandler()
}
func update_my_table() {
DispatchQueue.main.async {
tableView.reloadData()
}
}