import XCTest
class TestClass: XCTestCase
{
override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
// In UI tests it is usually best to stop immediately when a failure occurs.
continueAfterFailure = true
// UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method.
XCUIApplication().launch()
// UIApplication.shared.keyWindow?.layer.speed = 100
UIApplication.sharedApplication().keyWindow?.layer.speed = 100
// In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
}
func testExample()
{
}
func testA1_TC8InvalidEmailAndPasswordTC8_A1()
{
let app = XCUIApplication()
let emailIdTextField = app.textFields["Email ID"]
emailIdTextField.tap()
if emailIdTextField.value as! String != ""
{
emailIdTextField.clearAndEnterText(emailIdTextField.value as! String)
}
emailIdTextField.typeText("test#gmail.com")
let passwordSecureTextField = app.secureTextFields["Password"]
passwordSecureTextField.tap()
passwordSecureTextField.typeText("dgbnnnbb")
app.buttons["Login"].tap()
app.buttons["OK"].tap()
}
func testA2_TC9KeepAllFieldBlankTC9_A2()
{
let app = XCUIApplication()
let emailIdTextField = app.textFields["Email ID"]
emailIdTextField.tap()
if emailIdTextField.value as! String != ""
{
emailIdTextField.clearAndEnterText(emailIdTextField.value as! String)
}
app.textFields["Email ID"].tap()
let passwordSecureTextField = app.secureTextFields["Password"]
app.secureTextFields["Password"].tap()
if passwordSecureTextField.value as! String != ""
{
passwordSecureTextField.clearAndEnterText(passwordSecureTextField.value as! String)
}
app.secureTextFields["Password"].tap()
app.buttons["Login"].tap()
app.buttons["OK"].tap()
}
func testTC10ValidUsernameAndInvalidPasswordTC10C()
{
let app = XCUIApplication()
let emailIdTextField = app.textFields["Email ID"]
emailIdTextField.tap()
if emailIdTextField.value as! String != ""
{
emailIdTextField.clearAndEnterText(emailIdTextField.value as! String)
}
emailIdTextField.typeText("gfdedkff#gmail.com")
let passwordSecureTextField = app.secureTextFields["Password"]
passwordSecureTextField.tap()
passwordSecureTextField.typeText("grtegrgrtst")
let moreNumbersKey = app.keys["more, numbers"]
moreNumbersKey.tap()
passwordSecureTextField.typeText("#123")
app.buttons["Login"].tap()
app.buttons["OK"].tap()
}
I have multiple functions in test class. I want to use these functions one by one (synchronously) in order because function one result is function two output or parameter. Can you help me to find-out the solution?
in our project we had a similar issue. You cannot force tests execution order on XCTest. We resolved our problem by a method that describes all steps for each test, then we could execute whole path every time and check only specific points in each test.
To check if it works for example code you provided, I created test app >>here<<. The repository is on GitHub and required a bit of work, so I would be grateful if you could take a look at it. There is a basic form I needed for testing and as little code as I could write to test my solution. The most important part are UI tests, you can see two versions there. One in vanilla XCTest and one refactored to use a library I'm developing with my colleagues - AutoMate. It let me clean up the code.
As I was testing this solution in your case, it came to me that you may be tied with a state persisted between launches. That's why I introduced option which can be passed to the application.
var app: XCUIApplication!
override func setUp()
{
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
// In UI tests it is usually best to stop immediately when a failure occurs.
continueAfterFailure = true
// UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method.
app = XCUIApplication()
app.launchEnvironment["NUMBER_OF_ATTEMPTS"] = "0"
app.launch()
// UIApplication.shared.keyWindow?.layer.speed = 100
// UIApplication.sharedApplication().keyWindow?.layer.speed = 100
}
func testA1_TC8InvalidEmailAndPasswordTC8_A1()
{
setUp(forTest: 1)
// Do Your asserts here
}
func testA2_TC9KeepAllFieldBlankTC9_A2()
{
setUp(forTest: 2)
// Do Your asserts here
}
func testTC10ValidUsernameAndInvalidPasswordTC10C()
{
setUp(forTest: 3)
// Do Your asserts here
}
func setUp(forTest testsCount: Int) {
let credentials = [
("test#gmail.com", "dgbnnnbb"),
("", ""),
("gfdedkff#gmail.com", "grtegrgrtst")
]
let emailIdTextField = app.textFields["Email ID"]
let passwordSecureTextField = app.secureTextFields["Password"]
for (no, (email, password)) in credentials.prefix(upTo: testsCount).enumerated() {
fill(emailIdTextField, with: email)
switch no {
case 0:
fill(passwordSecureTextField, with: password, clear: false)
case 1:
fill(passwordSecureTextField, with: password)
passwordSecureTextField.tap()
case 2:
fill(passwordSecureTextField, with: password, clear: false)
let moreNumbersKey = app.keys["more, numbers"]
moreNumbersKey.tap()
passwordSecureTextField.typeText("#123")
default:
break
}
app.buttons["Login"].tap()
app.buttons["OK"].tap()
}
}
func fill(_ field: XCUIElement, with text: String, clear: Bool = true) {
field.tap()
if clear, let stringValue = field.value as? String, stringValue != ""
{
field.clearAndEnterText(field.value as! String)
}
field.typeText(text)
}
This testing framework that you're using (XCTest provided by Apple) is designed such that tests are independent of one another. This is for very (many) good reasons that is out of scope for this answer.
You can do it but you'll be fighting the framework the whole way and or losing out on key functionality provided to you by Xcode and the XCTest framework.
A quick an easy way to do this, is to just have one test() func. Then write all your individual tests as helper functions and call them sequentially within func test().
So...
func testSequentially() {
assertThisFirst()
assertThisSecond()
}
func assertThisFirst() {
// Some test
}
func assertThisSecond() {
// Some other test
}
Now let me reassert that this is not a good way of running tests. You miss out on the ability to run individual tests. You miss out on a lot of nice GUI in Xcode for inspecting tests. Your tests will be a lot harder to debug when failures inevitably do occur. On and on and on... I wish you the best of luck!
You can try this code:
let randomFunction = [test1(),test2(),test3()]
testMethodA()
testMethodB()
testMethodC()
Related
I am wanting to test the existence of a UIRefreshControl inside a UI Test. I define my control all init:
itemRefreshControl.accessibilityIdentifier = "MyRefreshIndicator"
// allow UITest to find the refresh
if let refreshLabel = itemRefreshControl.subviews.first?.subviews.last as? UILabel {
refreshLabel.isAccessibilityElement = true
refreshLabel.accessibilityIdentifier = "MyRefreshLabel"
}
Then in my test case I have tried:
let refreshCtlQuery = NSPredicate(format: "label CONTAINS[c] 'Refreshing'")
let refreshControl = app.staticTexts.containing(refreshCtlQuery)
expectation(for: exists, evaluatedWith: refreshControl, handler: nil)
start.press(forDuration: 0, thenDragTo: finish)
print(app.debugDescription)
waitForExpectations(timeout: 5, handler: nil)
I also tried:
let refreshControl = app.staticTexts["MyRefreshLabel"]
and I tried:
let refreshControl = app.activityIndicators["MyRefreshIndicator"]
In all those cases I can see the test runner perform the drag and I see the refresh control in the UI, but the expectation always fails. It's almost like the test blocks until the refreshing is done and then checks for existence and it's not there. When I print out the view hierarchy, I can't find the UIRefreshControl's label. How best can I test this?
Indeed, while UIRefreshControl does its animation, the tests are hang up saying "Wait for <BUNDLE_IDENTIFIER> to idle".
You can swizzle XCUIApplicationProcess.waitForQuiescenceIncludingAnimationsIdle: to an empty method, so you can bypass this behaviour (based on this answer).
extension XCTestCase {
static var disabledQuiescenceWaiting = false
/// Swizzle `XCUIApplicationProcess.waitForQuiescenceIncludingAnimationsIdle(:)`
/// to empty method. Invoke at `setUpWithError()` of your test case.
func disableQuiescenceWaiting() {
// Only if not disabled yet.
guard Self.disabledQuiescenceWaiting == false else { return }
// Swizzle.
if
let `class`: AnyClass = objc_getClass("XCUIApplicationProcess") as? AnyClass,
let quiescenceWaitingMethod = class_getInstanceMethod(`class`, Selector(("waitForQuiescenceIncludingAnimationsIdle:"))),
let emptyMethod = class_getInstanceMethod(type(of: self), #selector(Self.empty))
{
method_exchangeImplementations(quiescenceWaitingMethod, emptyMethod)
Self.disabledQuiescenceWaiting = true
}
}
#objc func empty() {
return
}
}
Then call at the setup of your test case.
override func setUpWithError() throws {
disableQuiescenceWaiting()
}
You can mark the UIRefreshControl directly (I made this extension for convenience).
extension UIRefreshControl {
func testable(as id: String) -> UIRefreshControl {
self.isAccessibilityElement = true
self.accessibilityIdentifier = id
return self
}
}
// Create the instance like this.
UIRefreshControl().testable(as: "RefreshControl")
So you can nicely assert the existence (get from otherElements).
let refreshControlElement = app.otherElements["RefreshControl"]
XCTAssertTrue(refreshControlElement.waitForExistence(timeout: 1))
Hi I am in desperate need for some help
All this is happening in a UIViewController child class
I am currently attaching the listener and populating an array and then feeding it to a UICollectionView in the following function (excuse some of the cut off code):
fileprivate func fetchNotes() { // This function is called in vidDidLoad()
let db = Firestore.firestore()
// Attaching listener (ie. listener is an attribute of the class)
listener = db.collection("Courses").document(titleForNavBar).collection("Notes")
.addSnapshotListener { snapshot, error in
// checking for any error
if error != nil {
self.arrayOfNotes.removeAll()
self.allNotesView.arrayOfNotes = self.arrayOfNotes
DispatchQueue.main.async {
self.allNotesView.allNotesCollectionView.reloadData()
}
return
} else {
self.arrayOfNotes.removeAll()
// if there is no error, the array holding all the objects is populated, in a for..loop
for document in (snapshot?.documents)! {
if let noteName = document.data()["noteName"] as? String,
let lectureInformation = document.data()["lectureInformation"] as? String,
let noteDescription = document.data()["noteDescription"] as? String,
let forCourse = document.data()["forCourse"] as? String,
let storageReference = document.data()["storageReference"] as? String,
let noteSize = document.data()["noteSize"] as? Int,
let rating = document.data()["rating"] as? Int
{
self.arrayOfNotes.append(Note(forCourse: forCourse, lectureInformation: lectureInformation, noteDescription: noteDescription, noteName: noteName, noteSize: noteSize, rating: rating, storageReference: storageReference))
self.allNotesView.arrayOfNotes = self.arrayOfNotes
// reloading the UICollectionView (on the main thread) so that it displays new data
DispatchQueue.main.async {
self.allNotesView.allNotesCollectionView.reloadData()
}
}
}
}
}
}
When the view disappears, I am also removing the listener
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(true)
if listener != nil {
listener.remove()
}
print("listener removed")
}
This works fine, when I install the application for the first time on any device or simulator. When I try to launch the controller, the second time, I get a very nasty error that I have no idea how to debug.
To be accurate the console throws this error:
NoteShare[97230:10528984] *** Assertion failure in -[FSTLevelDBRemoteDocumentCache decodedMaybeDocument:withKey:], third_party/firebase/ios/Source/Firestore/Source/Local/FSTLevelDBRemoteDocumentCache.mm:152
I know this question is quite long (sorry about that), but have you guys come across this error. Please give some hint on how to solve this problem. Thanks! If you need to see any other piece of my code, please let me know.
It seems to be failing here. I don't see what you could be doing wrong in your code to cause that, so you may have hit a bug. It seems very similar to this issue, which has been fixed in the repo but not been released.
I'm noticing an interesting behaviour. I've been testing the performance of constructing a linked list with many elements. For some reason, past a certain amount of deallocations, the test with crash.
Here's my LinkedList implementation:
class LinkedList<T> {
let data: T
var next: LinkedList?
init(data: T, next: LinkedList? = nil) {
self.data = data
self.next = next
}
func cons(_ data: T) -> LinkedList {
return LinkedList(data: data, next: self)
}
}
I am testing this using the XCTest library. I made this test function:
let number = 104633
func testPerformanceExample() {
self.measure {
var list = LinkedList<Int>(data: 5)
for i in 0..<number {
list = list.cons(i)
}
}
}
I spent a fair amount of time trying to home into this number. It seems that if I try to construct a LinkedList with 104634 nodes, I get a Thread 1: EXC_BAD_ACCESS (code=2, address=0x7fff5a059ff8) crash, and the debug navigator shows a tower of LinkedList.deinit calls:
Another interesting thing is that if you move the list outside of the test function, it no longer crashes:
var list = LinkedList<Int>(data: 5)
func testPerformanceExample() {
self.measure {
for i in 0..<self.number {
self.list = self.list.cons(i)
}
}
}
I curious as to why a long series of deallocations can cause a crash. Thanks in advance!
EDIT:
This crash also occurs when you run the code outside of a XCTestCase. I've got this code in a UIViewController:
class ViewController: UIViewController {
let number = 1046340
override func viewDidLoad() {
super.viewDidLoad()
let date = Date()
var list = LinkedList<Int>(data: 0, next: nil)
for i in 0..<number {
list = list.cons(i)
}
let timeInterval = Date().timeIntervalSince(date)
print(timeInterval)
}
}
I could not compile your test.
This code worked with 100x as many nodes. I don't think the self reference is your problem but it does indicate that you are using an older Swift version. Upgrade your tools and try again.
I want to test this method that doesn't return a value but I want to check if works fine.
Can you give me some suggestions?
func login() {
if Utility.feature.isAvailable(myFeat) {
if self.helper.ifAlreadyRed() {
self.showWebViewController()
} else {
let firstVC = FirstViewController()
self.setRootController(firstVC)
}
} else {
let secondVC = SecondViewController()
self.setRootController(secondVC)
}
}
so what's the best approach to apply unit test here?
Testing side effects is one approach. But for an example like the code in question, I actually prefer a subclass-and-expect approach.
Your code has three different paths.
If feature is available and already red, show web view controller.
If feature is available and not already red, show first view controller.
If feature is not available, show second view controller.
So assuming this login() function is part of FooViewController, one possibility is writing tests that follow this format:
func testLoginFeatureAvailableAndNotAlreadyRed() {
class TestVC: FooViewController {
let setRootExpectation: XCTExpectation
init(expectation: XCTExpectation) {
setRootExpectation = expectation
super.init()
}
override func setRootController(vc: UIViewController) {
defer { setRootExpectation.fulfill() }
XCTAssertTrue(vc is FirstViewController)
// TODO: Any other assertions on vc as appropriate
// Note the lack of calling super here.
// Calling super would inaccurately conflate our code coverage reports
// We're not actually asserting anything within the
// super implementation works as intended in this test
}
override func showWebViewController() {
XCTFail("Followed wrong path.")
}
}
let expectation = expectationWithDescription("Login present VC")
let testVC = TestVC(expectation: expectation)
testVC.loadView()
testVC.viewDidLoad()
// TODO: Set the state of testVC to whatever it should be
// to expect the path we set our mock class to expect
testVC.login()
waitForExpectationsWithTimeout(0, handler: nil)
}
Got a strange thing that's tripping me up. Feels like there's a simple "In Swift 2 we always (or never) do this" that I'm missing, but I can't see it.
I have a Brain class, meant to be used as a singleton:
class Brain: NSObject {
static var sharedInstance : Brain?
var languageLoadedAndReadyFunction = languageLoadedAndReadyImplementation
init() {
NSNotificationCenter.defaultCenter().addObserver(self,
selector: "languageLoadedAndReady",
name: Notifications.LanguageLoadedAndReady,
object: nil)
}
func languageLoadedAndReadyImplementation() {
print("Got language Ready notification")
}
func languageLoadedAndReady() {
self.languageLoadedAndReadyFunction(self)()
}
class func get() -> Brain! {
return sharedInstance
}
//...
class func reset() {
sharedInstance = Brain()
}
deinit() {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
}
In my unit test for Brain:
func testBrainRegisterForNotificationWhenWakingUp() {
let expectation = expectationWithDescription("Function should be called when the notification is received")
var brain = Brain.get()
brain.languageLoadedAndReadyFunction = {brain -> () -> () in
{
expectation.fulfill()
Brain.reset() // <-- this sets sharedInstance to a new Brain
}
}
brain.startUp() // <-- this causes the languageLoadedAndReady event to arrive at the brain
waitForExpectationsWithTimeout(5) {
error in
if let error = error {
print("Error: \(error.localizedDescription)")
}
}
}
The way I needed to define the function as "brain -> () -> ()" feels cumbersome, but it seems to work a treat. It let me see when the notification arrived, so I could verify, by test, that we're behaving correctly. The deinit() method is called after the brain is reset, indicating to me that the old brain is being removed rom memory.
Today, I'm writing some new tests for TranslatorTests.swift:
func testTranslatorCanTranslate() {
let expectation = expectationWithDescription("Function should be called when the notification is received2")
var brain = Brain.get()
brain.languageLoadedAndReadyFunction = {brain -> () -> () in
{
expectation.fulfill()
Brain.reset()
print("first TranslatorTests")
}
}
brain.startUp() // <-- 'brain' is different here than in BrainTests.swift, causes
// the closure in BrainTests.swift to be called.
waitForExpectationsWithTimeout(5) {
error in
if let error = error {
print("Error: \(error.localizedDescription)")
}
}
translator.setActiveLanguage("en")
let resultString = translator.translate("about_title")
XCTAssertEqual(resultString, "About")
}
func testTranslatorCanTranslateSecondWord() {
let expectation = expectationWithDescription("Function should be called when the notification is received3")
let brain = Brain.get()
brain.languageLoadedAndReadyFunction = {brain -> () -> () in
{
expectation.fulfill()
Brain.reset()
print("second TranslatorTests")
}
}
brain.startUp() // <-- 'brain' is different here than in BrainTests.swift, causes
// the closure in BrainTests.swift to be called.
waitForExpectationsWithTimeout(5) {
error in
if let error = error {
print("Error: \(error.localizedDescription)")
}
}
translator.setActiveLanguage("en")
let resultString = translator.translate("actmon_ready")
XCTAssertEqual(resultString, "Ready to upload")
}
When I run the tests, I get the most bizarre error: the closure in BrainTests.swift is called when the TranslatorTests.swift is executed. This causes the expectation.fulfill() method to be called a second time, causing a crash.
Incidentally, inside the closure there is no 'self' , but if I go up one level in the call stack, the 'self' refers to the previous instance of the brain. That leads me to belive that the brain -> () -> () syntax is the problem.
This boggles me. The 'brain' has a different address before each of the closures, which indicates to me that it's a different instance. In fact, the old brain has been deinited by this point, so how could it be called?
I would have thought that assigning to the variable for an instance would mean that we're giving the closure a new function to execute for this instance.
Can anyone explain this to me? Please use small words, I'm feeling a little less intelligent than I did when writing this code.
In my research, I stumbled across these two articles that helped:
http://oleb.net/blog/2014/07/swift-instance-methods-curried-functions/
https://devforums.apple.com/message/1008188#1008188
That explains to me why the call is "{brain -> () -> () in...".
The problem I had in my code was, of course, that the instance variable was declared like this in my test files:
class BrainTests: XCTestCase {
var brain: Brain!
override func setUp() {
super.setUp()
brain = Brain.get()
}
override func tearDown() {
super.tearDown()
Brain.reset()
}
func testBrainExists() {
XCTAssertNotNil(brain)
}
So, when I was calling Brain.reset() to drop the old singleton and create a new one, there was still a pointer to the original.
By removing the instance variable, the brain was reset as expected, and the callbacks then worked. I was able to set callbacks from each test case as needed to pass all my tests.
Goodness, this was a sneaky thing!