XCTest Unit Test data response not set in test after viewDidLoad
Problem
On the second Assertion you expect data which are loaded asynchronously, but you check them synchronously and at the time you are checking for them, the network operation has not finished yet, so of course it is going to fail.
Solution
Testing asynchronously is available on iOS using XCTestExpectation.
Example of use
let testExpectation = expectation(description: "Asynchronous expectation")
let sut = SubjectUnderTest()
sut.executeAsynchronousOperation(completion: { (error, data) in
XCTAssertTrue(error == nil)
XCTAssertTrue(data != nil)
testExpectation.fulfill()
})
waitForExpectations(timeout: 5, handler: .none)
Tips
Another thing to point out is that testing network operations and waiting for the response is a bad practice, you should intercept the network requests and return local data instead of waiting for the real data from the server. (On GitHub there is a nice framework to do that in Swift: OHHTTPStubs)
How to unit test `viewDidLoad()`
Why do you need to use the storyboard to create the view controller?
Wouldn't this work?
func testThatServiceIsCalled() {
let vc = MyViewController()
vc.service = serviceMock
vc.viewDidLoad()
XCTAssertTrue(serviceMock.loadCalled)
}
UnitTests and iOS : viewDidLoad triggered twice
You need to access the view in order to have it load automatically.
You can use something like this to do it without side effects:
vc.view.hidden = NO; // Or YES if it is supposed to be hidden.
Oh, and then remove your manual call to viewDidLoad
as it won't be needed.
IOS Why Unit Test a class calls ViewController
It's creating an instance of ViewController
because your app creates a ViewController
at launch, and your test bundle uses your app as its “Host Application”:
When the setting is configured like shown above, it means Xcode runs the tests in your test bundle by launching the host application, then injecting the test bundle into it.
You can change the “Host Application” setting to “None”:
When the setting is configured as “None”, Xcode runs your test bundle using a non-GUI app called the XCTest agent, and doesn't launch your app, so your app doesn't get the chance to create a ViewController
.
Note that when you set “Host Application” to “None”, your test bundle can no longer access any APIs defined in the application. Any APIs you want to test, you have to move into a framework, and link the test bundle with that framework (in the “Build Phases” tab for the test bundle).
Note also that even when you set “Host Application” to “None”, Xcode still launches a simulator. Your test cases are allowed to use iOS system services that are only available when a simulator is running, and Xcode doesn't know in advance whether your test cases need those services. So it has to launch the simulator first, just in case.
Use of undeclared type 'ViewController' when unit testing my own ViewController in Swift?
Swift 1
You should add ViewController.swift file's target membership also as your test target also if you are not using framework. Select class file add to target as shown in image:
OR
If you are ViewController is within a framework : ViewController
class is in different target and You are not declaring class with public access level. By default Classes are internal (Accessible within a target). Declare it as public and also make methods or properties as public if you want to access it i.e
public class ViewController: UIViewController {
public var content: String!
override public func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override public func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Swift 2 Update
In your test target, just import the module you want to test using the @testable
keyword:
@testable import moduleToTest
You can now access public
and internal
symbols in your test target.
swift 2 Xcode 7 unit testing
Unit Test button added to view
The viewDidLoad
is not called in your test case, therefore it fails. You have to call it manually.
1. Introduce an extension
extension UIViewController {
func startViewLifecycle() {
view.setNeedsLayout()
view.layoutIfNeeded()
}
}
2. Call it in your test case:
func testAddRoomButtonInRoomsViewController() {
let vc = myViewController()
vc.startViewLifecycle()
XCTAssertNotNil(vc.myButton, "Button Not Initialised")
XCTAssertNotNil(vc.myButton.superview, "Button Not Added To View")
}
Related Topics
Updated Approach to Reauthenticate a User
Custom Cell with Uitableview Inside Uicollectionviewcell
How Does One Use Nsdateformatter's Islenient Option
"Ambiguous Use of 'Children'" When Trying to Use Nstreecontroller.Arrangedobjects in Swift 3.0
Composing Video and Audio - Video's Audio Is Gone
Randomize Two Arrays the Same Way Swift
Requestaccessformediatype Doesn't Ask for Permission
Empty Class in Swift Playground Gives _Lldb_Expr_ Error
How to Read Code Block After a Function Call
After Getting Image from Uiimagepickercontroller, Uiimageview Rotates Image for iPhone 5
Cannot Invoke "+=" with an Argument List of Type (Int, @Value Int)
Is There Difference Between Using a Constructor and Using .Init
Why Do We Need to Specify Init Method
How to Decode Partially Double Serialized JSON String Using 'Codable' Protocol