In what situation would one use expectationForNotification in swift testing
As you have already imagined expectationForNotification is a convenience expectation for checking if a notification was raised.
This test:
func testItShouldRaiseAPassNotificationV1() {
let expectation = expectationWithDescription("Notification Raised")
let sub = NSNotificationCenter.defaultCenter().addObserverForName("evPassed", object: nil, queue: nil) { (not) -> Void in
expectation.fulfill()
}
NSNotificationCenter.defaultCenter().postNotificationName("evPassed", object: nil)
waitForExpectationsWithTimeout(0.1, handler: nil)
NSNotificationCenter.defaultCenter().removeObserver(sub)
}
can be replaced by this one:
func testItShouldRaiseAPassNotificationV2() {
expectationForNotification("evPassed", object: nil, handler: nil)
NSNotificationCenter.defaultCenter().postNotificationName("evPassed", object: nil)
waitForExpectationsWithTimeout(0.1, handler: nil)
}
You can find a good explanation in this Objc.io number.
XCTest - Unable to understand / implement expectations in unit tests (to test aysnc code)
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 write unit test for receiving nsnotification asynchronous?
You can add an expectation for the notification:
expectationForNotification("BlaBlaNotification", object: nil) { (notification) -> Bool in
// call the method that fetches the data
sut.fetchData()
waitForExpectationsWithTimeout(5, handler: nil)
But personally I would split this is two tests. One for the fetching of the data (tested using a stub) and one for the sending of the notification.
Nothing in return value
use completion because you in asynchronous block. Try this
func fetchUsers(ref:FIRDatabaseReference, completion(users: [Users])) {
var newUsers = [User]()
ref.observeEventType(.Value, withBlock: {snapshot in
for user in snapshot.children {
let users = User(snapshot: user as! FIRDataSnapshot)
newUsers.append(users)
}
print("closure exited. There are \(newUsers.count) Users in newUsers")
completion(users: newUsers)
})
}
Related Topics
Xcode 8 Shell Script Invocation Error
Swiftui - Pass Data to Different Views
Converting Audiobuffer to Cmsamplebuffer with Accurate Cmtime
How to Draw Line Node Keep Same Size in Camera as Measure App in Iphone
How to Access Cfdictionary in Swift 3
Having Trouble with Nstimer (Swift)
Make a Type Itself -- Not Its Instances -- Conform to a Protocol
Swift: Animate Object Along Animating Path
iOS 13, Custom Image, Title and Subtitle in the Presented Uiactivityviewcontroller
How to Process an Array of Task Asynchronously with Swift Combine
Swift 3/Macos: Open Window on Certain Screen
Contextmenu on a Rounded Lineargradient Produces Sharp Edges in Swiftui
Swift Extension Storage for Protocols
Need Detailed Explanation for Memoize Implementation in Swift (Wwdc 14, Session 404)
Could Not Find an Overload for '+' That Accepts the Supplied Arguments