Swift Unit Test function that async dispatches a block that sets a variable I want to test - swift

I have some code that is like the following
class Vibration: NSObject {
var status: VibrationStatus // an enum
}
and a function on another class (of type NSObject) like the following, that is part of an object that has a property vibration of type Vibration
func vibrate() {
DispatchQueue.main.async { [weak self] in
vibration.status = .vibrating
// do some real HW vibrate stuff
}
}
None of the properties or class definitions include #objc or #objcMembers
I am trying to create a test that will wait for that async call to set the vibration.status.
I have a test function that seems to work (see below) when I declare the status property as #objc or put #objcMembers on the Vibration class.
func testVibrate() {
let invite: SignalingInviteBody = SignalingInviteBody()
let incomingCall = IncomingCall(invite)
let expectation = XCTNSPredicateExpectation(predicate: NSPredicate(format: "status = 2"), object: incomingCall.vibration)
incomingCall.startRing() // this calls the function vibrate()
wait(for: [expectation], timeout: 3.0)
}
This test also requires #objc on the enum declaration with Objective-C compatible enum declaration, which I don't want as it would only be for testing.
Except for testing, there is no need to make the status property #objc or the Vibration class as #objcMembers and I would rather not change the base program code to a more inefficient style when I don't need the Objective-C compatibility in the base program.
Is there a way to unit test this in a real, honest to goodness, Swift way?

Preface: this is just some pseudo-code I nailed out quickly in the browser. It'll probably need some polishing before it compiles properly.
I would use a mock and dependency injection:
class MockVibration: Vibration {
let statusChanged: (VibrationStatus) -> Void
init(statusChanged: (VibrationStatus) -> Void) {
self.statusChanged = statusChanged
}
var status: VibrationStatus {
didSet {
statusChanged(status)
}
}
}
I would probably have a protocol, and have Vibration and MockVibration both conform to it, but having MockVibration: Vibration should work well. Once you've defined this mock, you can use it to fulfill an expectation in your test case:
func testVibrate() {
let didVibrate = self.expectation(description: "Started vibrating")
let mockVibration = MockVibration { newStatus in
XCTAssertEqual(newStatus, .vibrating)
didVibrate.fulfill()
}
let invite = SignalingInviteBody()
let incomingCall = IncomingCall(invite, mockVibration)
incomingCall.startRing() // this calls the function vibrate()
wait(for: [didVibrate], timeout: 3.0)
}
You might even be able to rework this interface so that DispatchQueue.main.async {} happens within the Vibration class as an internal detail. If you do that, the interaction between IncomingCall and Vibration becomes synchronous, so you wouldn't need to use expectations. Your incoming call test would reduce to:
class MockVibration {
// The mock can just have its status set simply/synchronously
var status: VibrationStatus = .off // or some other "initial" value
}
func testVibrate() {
let mockVibration = MockVibration()
let invite = SignalingInviteBody()
let incomingCall = IncomingCall(invite, mockVibration)
incomingCall.startRing() // this calls the function vibrate()
XCTAssertEqual(mockVibration.status, .vibrating)
}
Of course, then you'd need a separate test that covers Vibration, and ensures that its public APIs cause it to change its internal state using the right dispatch queue or whatever.

Related

Unit Test: Evaluate Singleton into Function

I need your help, this time for Unit Test and how to evaluate a Singleton into Function. This is my context:
I have a class, which it has a Singleton like this:
class ClassToTest (){
var testMessage = ""
init(){
// This is only a empty init
}
func funcWhichHasASingleton() {
testMessage = "Function was covered"
MySingleton.shared.sendAnalytics("params")
}
}
As you can see, there is a singleton where is sending analytics
MySingleton.shared.sendAnalytics("params")
And I have my test function like this:
func testExample() {
// Given
let sut = ClassToTest()
// When
sut.funcWhichHasASingleton()
// Then
XCTAssertEqual(sut.testMessage, "Function was covered")
}
The my question is: how I can test the Singleton into ClassToTest, such as Xcode pass into that function but my SonarQube says I need to cover the Singleton line. How can I do that?
Breaking this down a little, what you actually need to to test is not the function being called but what the function does. In this case, I think you want to make sure the analytics are being called.
In which case you would need to do something like:
class ClassToTest {
let analytics: MySingleton
init(analytics: MySingleton) {
self.analytics = analytics
}
func funcWhichHasASingleton() {
analytics.sendAnalytics("params")
}
}
Then you are in a position to test the analytics, possibly with a mock.
func testExample() {
let analytics = MySingleton.shared // or better, some kind of Mock class.
// Given
let sut = ClassToTest(analytics: analytics)
// When
sut.funcWhichHasASingleton()
// Then
XCTAssertEqual(analytics.messageCount, 1) // You would need to be able to monitor what the singletons does.
}
Potentially, if you can have a mock MySingleton, then you can do this. Maybe using Mockingbird or use a protocol witness.
let didSendSendExpectation = expectation("")
analytics.didRecieve { message in
didSendSendExpectation.fulfill()
}
// When
sut.funcWhichHasASingleton()
wait(for: didSendSendExpectation, timeout: 0.1)

XCTest for when iCloud is enabled and disabled

I have a viewMode that determines if iCloud is enabled or disabled with the result being a prompt to the User to login to iCloud or not.
Is there a way to progamatically login/logout to iCloud from an XCTest to reliable test all paths?
Here is my test
func testShowLoginButtonForiCloud() {
let viewModel = OnboardingViewModel()
let expectation = XCTestExpectation(description: "Wait for CKContainer auth check")
var iCloudEnabled: Bool?
viewModel.shouldShowiCloudLogin { result, error in
iCloudEnabled = result
expectation.fulfill()
}
wait(for: [expectation], timeout: 5.0)
XCTAssertNotNil(iCloudEnabled)
XCTAssertFalse(iCloudEnabled!)
}
Here is my ViewModel
typealias Completion = (Bool, Error?) -> Void
final class OnboardingViewModel {
func shouldShowiCloudLogin(completion: #escaping Completion) {
CKContainer.default().accountStatus { (status, error) in
switch status {
case .available :
completion(true, nil)
default :
completion(false, error)
}
}
}
}
Can we programmatically log in to CloudKit for unit testing? This is inadvisable, because even if we could, the tests would be slow & fragile. Instead, treat CloudKit as an architectural boundary. Unit tests can go right up to this boundary. And we can pretend stuff comes back from the boundary. In this way, we can test all paths.
To program this boundary into your code, use a protocol. This protocol will be a slice containing only those CKContainer methods you want. (This is the Interface Segregation Principle in action.) Since CKContainer already implements this method, we can attach it as an empty extension.
protocol CKContainerProtocol {
func accountStatus(completionHandler: #escaping (CKAccountStatus, Error?) -> Void)
}
extension CKContainer: CKContainerProtocol {}
Then add a property to your view model:
var cloudKitContainer: CKContainerProtocol = CKContainer.default()
The default value means your code will continue to use the real CKContainer unless told otherwise. Change your code to call cloudKitContainer instead of CKContainer.default().
Then in test code, you can provide a different implementation of CKContainerProtocol. This will let you do stubbing and mocking. You can confirm that accountStatus() is called exactly once. And you can exercise its closure with different CKAccountStatus values to confirm how your Completion closure is called.

KVO for dependent key paths does not work properly for Swift class

I'm trying to write a wrapper around URLSessionTask in Swift. According to the documentation
All task properties support key-value observing.
So I want to keep this behavior and make all the properties on my wrapper also KVO-compliant (usually delegating to the wrapped task) and fully accessible to Objective-C. I'll describe what I'm doing with one property, but I basically want to do the same thing for all properties.
Let's take the property state of URLSessionTask. I create my wrapper like this:
#objc(MyURLSessionTask)
public class TaskWrapper: NSObject {
#objc public internal(set) var underlyingTask: URLSessionTask?
#objc dynamic public var state: URLSessionTask.State {
return underlyingTask?.state ?? backupState
}
// the state to be used when we don't have an underlyingTask
#objc dynamic private var backupState: URLSessionTask.State = .suspended
#objc public func resume() {
if let task = underlyingTask {
task.resume()
return
}
dispatchOnBackgroundQueue {
let task:URLSessionTask = constructTask()
task.resume()
self.underlyingTask = task
}
}
}
I added #objc to the properties so they are available to be called from Objective-C. And I added dynamic to the properties so they will be called via message-passing/the runtime even from Swift, to make sure the correct KVO-Notifications can be generated by NSObject. This is supposed to be enough according to Apple's KVO chapter in the "Using Swift with Cocoa and Objective-C" book.
I then implemented the static class methods necessary to tell KVO about dependent key paths:
// MARK: KVO Support
extension TaskWrapper {
#objc static var keyPathsForValuesAffectingState:Set<String> {
let keypaths:Set<String> = [
#keyPath(TaskWrapper.backupState),
#keyPath(TaskWrapper.underlyingTask.state)
]
return keypaths
}
}
Then I wrote a unit test to check whether the notifications are called correctly:
var swiftKVOObserver:NSKeyValueObservation?
func testStateObservation() {
let taskWrapper = TaskWrapper()
let objcKVOExpectation = keyValueObservingExpectation(for: taskWrapper, keyPath: #keyPath(TaskWrapper.state), handler: nil)
let swiftKVOExpectation = expectation(description: "Expect Swift KVO call for `state`-change")
swiftKVOObserver = taskWrapper.observe(\.state) { (_, _) in
swiftKVOExpectation.fulfill()
}
// this should trigger both KVO versions
taskWrapper.underlyingTask = URLSession(configuration: .default).dataTask(with: url)
self.wait(for: [swiftKVOExpectation, objcKVOExpectation], timeout: 0.1)
}
When I run it, the test crashes with an NSInternalInconsistencyException:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Cannot remove an observer <_XCKVOExpectationImplementation 0x60000009d6a0> for the key path "underlyingTask.state" from < MyURLSessionTask 0x6000002a1440>, most likely because the value for the key "underlyingTask" has changed without an appropriate KVO notification being sent. Check the KVO-compliance of the MyURLSessionTask class.'
But by making the underlyingTask-property #objc and dynamic, the Objective-C runtime should ensure that this notification is sent, even when the task is changed from Swift, right?
I can make the test work correctly by sending the KVO-notifications for the underlyingTask manually like this:
#objc public internal(set) var underlyingTask: URLSessionTask? {
willSet {
willChangeValue(for: \.underlyingTask)
}
didSet {
didChangeValue(for: \.underlyingTask)
}
}
But I'd much rather avoid having to implement this for every property and would prefer to use the existing keyPathsForValuesAffecting<Key> methods. Am I missing something to make this work? Or should it work and this is a bug?
Property underlyingTask isn't dynamic.

Avoid coupling when using enum in tests

Say we have this enum
enum Action: String {
case doThing
case doOtherThing
}
This enum is used this way:
func run(action: Action, block: () -> Void)
Now, I unit test the run method so I need to pass an Action this way:
func testActionRun() {
let expect = expectation(description: #function)
let sut = ActionRunner()
sut.run(action: .doThing) {
expect.fulfill()
// Assert something
}
waitForExpectations(timeout: 0.1, handler: nil)
}
As I need to test other situations on ActionRunner, I ended with a lot of .doThing spread over the whole test suite.
The problem is: if I make a change in production code and change case doThing to case doThatThing now all my test suite fails because there is no a case doThing.
The perfect thing would be to declare a dummy case in test code to allow something like
sut.run(action: .dummyAction) {
}
but enum does not allow that as it doesn't allows inheritance nor a extension to add a case.
The first option that came to my mind was to convert Action into a protocol, but that change is unnecessary in production and its only purpose is to accomplish something in test code.
So, is it there another option to achieve this?
The question of how to avoid coupling when using enums is a tricky one. I bumped into that myself a few times with no solid answer :/
One point you raise is the one of using a protocol, and that feels unnecessary in production. I sort of agree with that, but most time it's the necessary evil.
In the example you showed though I think maybe a tweak in the design might solve part of the problem.
In particular when looking at this code
func run(action: Action, block: () -> Void) {
// ...
}
func testActionRun() {
let expect = expectation(description: #function)
let sut = ActionRunner()
sut.run(action: .doThing) {
expect.fulfill()
// Assert something
}
waitForExpectations(timeout: 0.1, handler: nil)
}
What comes to mind to me is that your Action specifies a certain behaviour. That is when you test the run method passing .doThing you expect a different behaviour than when passing .doOtherThing.
If that's right, is there any reason why you need to pass the action enum instance and an action block to the run function?
You could separate the code that defines the behaviour from the one performs the actual action even more that what you've done already. For example:
protocol Actionable {
var action: () -> () { get }
}
enum Action: Actionable {
case doThing
case doOtherThing
var action {
switch self {
case .doThing: return ...
case .doOtherThing: return ...
}
}
class ActionRunner {
func run(actionable: Actionable) {
actionable.action()
}
}
func testActionRun() {
let expect = expectation(description: #function)
let sut = ActionRunner()
sut.run(actionable: FakeActionable()) {
expectation.fulfill()
}
waitForExpectations(timeout: 0.1, handler: nil)
}
class FakeActionable: Actionable {
let action = { }
}
func testDoThing() {
let sut = Action.doThing
sut.action()
// XCTAssert for the expected effect of the action
}
Note: I haven't actually compiled that code, so bear with me if it has some mistakes. It should give the idea though.
This way you have ActionRunner which only purpose is to properly run a given Actionable, and the Action enum which only purpose is to describe what different actions should do.
This example code is rather restrict in what it can do, only run () -> () actions, but you could build on top of it to achieve more advanced behaviours.
If you change your production code you have to change your test code too in order to test those new changes.
Maybe you can set the value on an Action variable in the setUp func of your XCTestCase class
import XCTest
class SharingKitTests: XCTestCase {
var theAction: Action!
override func setUp() {
super.setUp()
self.theAction = .doThing
}
}
Then you will be able to use this theAction var in all your test methods, and if you need to change the value you only need to change it in one place.

XCTest Single Asynchronous SetUp With Semaphores

I am working on testing an API through Alamofire. I need to make a single call to the server to prepare it for the integration test. Once that is done, I am ready to start running tests.
The usual override setUp() is run for every test, so I do not want to do that.
I have therefore chosen to override the class setUp() as described here: https://developer.apple.com/reference/xctest/xctestcase
That's all well and good, but now, I no longer can use the standard waitForExpectations. (In the class override setUp()) I get several compiler errors that tell me that I am no longer calling the same waitForExpectations because I am in a class method, not a test case.
To try to get around this, I wanted to use a semaphore like so:
class ServiceLayerTests: XCTestCase {
static var apiService: APIService = APIService()
let sessionManager = SessionManager(serverTrustPolicyManager: ServerTrustPolicyManager(policies: ["http://localhost:3000/": .disableEvaluation]))
static var companyManger: UserWebResource?
static var companyManagerID = -1
override class func setUp() {
apiService.baseURL = "http://localhost:3000/"
beginIntegrationTests()
}
class func beginIntegrationTests() {
var semaphore = DispatchSemaphore(value: 0)
apiService.beginIntegrationTests(completion: {resource, error in
if let resource = resource {
if let manager = resource as? UserWebResource {
companyManger = manager
companyManagerID = manager.id
semaphore.signal()
}
}
})
_ = semaphore.wait(timeout: DispatchTime.distantFuture)
}
}
This does not work. Under the hood, there is an alamo fire call to the server and it responds with the user to use for the integration tests. I do see the server spinning, so I know that the actual communication is happening, but I never get into the completion closure.
I suspect I am not understanding how Swift does semaphores and that I have created a deadlock somehow. If somebody has a better solution, I'd be more than happy to hear it.
I get several compiler errors that tell me that I am no longer calling
the same waitForExpectations because I am in a class method, not a
test case
That makes sense. What you probably want is to refactor so that you are in a test case:
override class func setUp() {
apiService.baseURL = "http://localhost:3000/"
}
func testIntegrationTests() {
let urlExpectation = expectation(description: "INTEGRATION TEST")
apiService.beginIntegrationTests(completion: {resource, error in
// ...
urlExpectation.fulfill()
})
// not sure what an acceptable timeout would be, I chose this at random
waitForExpectations(timeout: 25) { error in
if let error = error {
print("Error: \(error.localizedDescription)")
}
}
}
One of the best resources with some good test examples can be found here: http://nshipster.com/xctestcase/
You can create the expectation as a lazy var that executes your one-time set up and fulfills on completion.
At the beginning of your per-test setUp() function you can wait for that expectation.
None of your tests will run until it is fulfilled, and the initial setup will run only once.
class WaitForSetup_Tests: XCTestCase {
lazy var initialSetupFinished: XCTestExpectation = {
let initialSetupFinished = expectation(description: "initial setup finished")
initialSetupTask() { // your setup task that executes this closure on completion
initialSetupFinished.fulfill()
return
}
return initialSetupFinished
}()
override func setUp() {
wait(for: [initialSignOutFinished], timeout: 2.0)
// do your per-test setup here
}
}
Note: This solution avoids using the override class function setUp() class method, because I couldn't figure out how to use the expectations except for in an instance.