OCMock: Why do I get an unrecognized selector exception when attempting to call a UIWebView mock?
This turned out to be one of those off by one character issues that you don't notice until you've looked at it a few dozen times.
Per this post on the OCMock forums, I had set my Other Linker Flags for my unit test target to -ObjC -forceload $(PROJECT_DIR)/Libraries/libOCMock.a
. This is wrong; -forceload
should have been -force_load
. Once I fixed this typo, my tests worked.
Swift XCTest: Verify proper deallocation of weak variables
The problem is that your testWeakVarDeallocation()
function hasn't exited when the dispatchAsync
block is called so a strong reference to strongMock
is still held.
Try it like this (allowing testWeakVarDeallocation()
to exit) and you'll see weakMock
becomes nil
as expected:
class weakTestTests: XCTestCase {
var strongMock: Mock? = Mock()
func testWeakVarDeallocation() {
weak var weakMock = strongMock
print("weakMock is \(weakMock)")
let expt = self.expectation(description: "deallocated")
strongMock = nil
print("weakMock is now \(weakMock)")
DispatchQueue.main.async {
XCTAssertNil(weakMock) // This assertion fails
print("fulfilling expectation")
expt.fulfill()
}
print("waiting for expectation")
self.waitForExpectations(timeout: 1.0, handler: nil)
print("expectation fulfilled")
}
}
How can I verify a class method is called using XCTAssert?
Create a property on your mock, mutating it's value within the method you expect to call. You can then use your XCTAssertEqual to check that prop has been updated.
func test_InitAuthCodeFlow_CallsRenderOAuthWebView() {
let renderOAuthWebViewExpectation = expectation(description: "RenderOAuthWebView")
class OAuthServiceMock: OAuthService {
var renderOAuthWebViewExpectation: XCTestExpectation!
var didCallRenderOAuthWebView = false
override func renderOAuthWebView(forService service: IdentityEndpoint, queryitems: [String: String]) {
didCallRenderOAuthWebView = true
renderOAuthWebViewExpectation.fulfill()
}
}
let sut = OAuthServiceMock(apiClient: mockAPIClient)
XCTAssertEqual(sut.didCallRenderOAuthWebView, false)
sut.renderOAuthWebViewExpectation = renderOAuthWebViewExpectation
sut.initAuthCodeFlow()
waitForExpectations(timeout: 1) { _ in
XCTAssertEqual(sut.didCallRenderOAuthWebView, true)
}
}
How to write do {} catch {} in Xcode unit test with 100% code coverage
As has been mentioned in the comments, you should expect your unit test code to not have full coverage; especially for the XCTFail
calls. The whole goal of the unit test is to never hit that line.
Even if you restructured your source to bring the XCTFail
somewhere else, you're still intending to have it never executed. You could accomplish more code coverage by using XCTAssertEqual
again.
func testUrlRequest_WithAuthenticationNoToken_ExpectingAuthenticationFailure() {
let mockController = MockAuthenticationController()
mockController.token = nil
Server.authenticationController = mockController
var failed = false
do {
_ = try Server.urlRequestWithHeaders(to: arbitraryEndpoint, excludeBearerToken: false)
} catch {
XCTAssertEqual(error as? Server.Errors, .authenticationFailure)
failed = true
}
XCTAssertEqual(failed, true, "Expected throw when no token is present")
}
Can you do test setup before application loads in Swift?
One way to do it would be to have an separate NSApplicationMain
for when unit tests are run vs one for "normal" runs.
First remove the @NSApplicationMain
annotation from your current AppDelegate
class. It should end up looking something like this:
AppDelegate.swift
import AppKit
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
print("Debug/Production run")
// Insert code here to initialize your application
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
}
Now create a new file called AppDelegateUnitTesting.swift
and it's source should look like this:
AppDelegateUnitTesting.swift
import Foundation
import Cocoa
class AppDelegateTesting: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
print("Unit Testing Run")
// Insert code here to initialize your application
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
}
Now add a new file called main.swift
this file will determine in which environment our app is running, the source should be something like this:
main.swift
import Foundation
import Cocoa
let isRunningTests = NSClassFromString("XCTestCase") != nil &&
ProcessInfo.processInfo.arguments.contains("-XCUnitTests")
fileprivate var delegate: NSApplicationDelegate?
if !isRunningTests {
delegate = AppDelegate()
NSApplication.shared.delegate = delegate
// See this Answer to initialize the Windows programmatically
// https://stackoverflow.com/a/44604229/496351
} else {
delegate = AppDelegateTesting()
NSApplication.shared.delegate = delegate
}
NSApplication.shared.run()
To determine whether it's running in a Unit Test environment it checks if it can load the XCTestClass
(which is only injected when testing) and it checks for the presence of the -XCUnitTest
command line argument, we have to set this argument ourselves as part of the Scheme's Test
action as shown in the image below
After doing all of this, you should see the message "Debug/Production run"
printed when you press the play button and you should see the message "Unit Testing Run"
printed whenever you run your unit tests.
You'll most likely have to add code to load the initial window programmatically this other answer shows how to do it:
- how to Load initial window controller from storyboard?
How to unit test throwing functions in Swift?
EDIT: Updated the code for Swift 4.1
(still valid with Swift 5.2
)
Here's the latest Swift version of Fyodor Volchyok's answer who used XCTAssertThrowsError
:
enum MyError: Error {
case someExpectedError
case someUnexpectedError
}
func functionThatThrows() throws {
throw MyError.someExpectedError
}
func testFunctionThatThrows() {
XCTAssertThrowsError(try functionThatThrows()) { error in
XCTAssertEqual(error as! MyError, MyError.someExpectedError)
}
}
If your Error
enum has associated values, you can either have your Error
enum conform to Equatable
, or use the if case
statement:
enum MyError: Error, Equatable {
case someExpectedError
case someUnexpectedError
case associatedValueError(value: Int)
}
func functionThatThrows() throws {
throw MyError.associatedValueError(value: 10)
}
// Equatable pattern: simplest solution if you have a simple associated value that can be tested inside 1 XCTAssertEqual
func testFunctionThatThrows() {
XCTAssertThrowsError(try functionThatThrows()) { error in
XCTAssertEqual(error as! MyError, MyError.associatedValueError(value: 10))
}
}
// if case pattern: useful if you have one or more associated values more or less complex (struct, classes...)
func testFunctionThatThrows() {
XCTAssertThrowsError(try functionThatThrows()) { error in
guard case MyError.associatedValueError(let value) = error else {
return XCTFail()
}
XCTAssertEqual(value, 10)
// if you have several values or if they require more complex tests, you can do it here
}
}
Related Topics
How to Test Required Init(Coder:)
How to Cast from Uint16 to Nsnumber
Swift - Exit Outer Function from Closure
Type of Expression Is Ambiguous Without More Context in Xcode 11
File Couldn't Be Opened Because You Don't Have Permission to View It Error
Swift Struct with Lazy, Private Property Conforming to Protocol
How to Switch to Swift 4.0 in Xcode 9.3
How to 'Addtarget' to Uilabel in Swift
Codable Enum with Multiple Keys and Associated Values
Mandatory Init Override in Swift Uinavigationcontroller Subclass
Counting Coloured Pixels on the Gpu - Theory
Integrate Existing Aws Cognito User Pool into iOS Project with Amplify
How to Set Priority on Constraints in Swift
Check If a Func Exists in Swift