Unable to access Main Target Methods when Adding Tests to OSX Project in Swift - swift

I have tried adding a testing bundle to my Project which seems to have succeeded.
However when I try to create instances of the Classes in my main project- I am unable to see them.
The Project seems to build fine - but I can' instantiate any of the test objects
Any ideas how to access them
Example Class to Test:
class EmailHelper: NSObject {
func generateEmailBody (greeting: String, bodyContent: String) -> String {
//Content goes in here
return message
}
}
import XCTest
class MyProject_DesktopTests: XCTestCase {
override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
}
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() {
// The Test would go in here but I can't seem to resolve EmailHelper class- it generates a lint error
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
func testPerformanceExample() {
// This is an example of a performance test case.
self.measureBlock {
// Put the code you want to measure the time of here.
}
}
}

I managed to get it working by adding Testable to the top of the class( This appears to be OSX specific issue)
import XCTest
#testable import MyProjectName // <--- This was the missing bit.... :)
class MyProject_DesktopTests: XCTestCase {
override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
}
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() {
// The Test would go in here but I can't seem to resolve EmailHelper class- it generates a lint error
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
func testPerformanceExample() {
// This is an example of a performance test case.
self.measureBlock {
// Put the code you want to measure the time of here.
}
}
}
Also be sure to clean your project after adding it an it seems to work.

Related

XCTest class func setUp() method how to handle failures

So Apple has an example of how the setUp and TearDown methods should be used that looks like this:
class SetUpAndTearDownExampleTestCase: XCTestCase {
override class func setUp() { // 1.
// This is the setUp() class method.
// It is called before the first test method begins.
// Set up any overall initial state here.
}
override func setUpWithError() throws { // 2.
// This is the setUpWithError() instance method.
// It is called before each test method begins.
// Set up any per-test state here.
}
override func setUp() { // 3.
// This is the setUp() instance method.
// It is called before each test method begins.
// Use setUpWithError() to set up any per-test state,
// unless you have legacy tests using setUp().
}
func testMethod1() throws { // 4.
// This is the first test method.
// Your testing code goes here.
addTeardownBlock { // 5.
// Called when testMethod1() ends.
}
}
func testMethod2() throws { // 6.
// This is the second test method.
// Your testing code goes here.
addTeardownBlock { // 7.
// Called when testMethod2() ends.
}
addTeardownBlock { // 8.
// Called when testMethod2() ends.
}
}
override func tearDown() { // 9.
// This is the tearDown() instance method.
// It is called after each test method completes.
// Use tearDownWithError() for any per-test cleanup,
// unless you have legacy tests using tearDown().
}
override func tearDownWithError() throws { // 10.
// This is the tearDownWithError() instance method.
// It is called after each test method completes.
// Perform any per-test cleanup here.
}
override class func tearDown() { // 11.
// This is the tearDown() class method.
// It is called after all test methods complete.
// Perform any overall cleanup here.
}
}
As you can see in the first method override class func setUp() overall inital state is suppost to be set. I have a simple setup phase for the application. But if this fails in some way. Then the test just get ignored like it never ran instead of marked as failure.
This is what the log says:
:0: error: simpleUITest : Failed to get matching snapshot: No
matches found for first query match sequence: Descendants matching type Cell -> Elements matching predicate '"login_button" IN identifiers', given input App element pid: 9849 (no attribute values
faulted in) Test Suite 'simpleUITest' failed at 2021-01-20
10:49:42.684. Executed 0 tests, with 1 failure (0 unexpected) in
0.000 (13.204) seconds
How can I get this to report as a test failure rather then just having XCTest ignore it?
Based on the docs, you have at least two options to fail a test from setUp:
Before each test begins, XCTest calls setUpWithError(), followed by setUp(). If state preparation might throw errors, override setUpWithError(). XCTest marks the test failed when it catches errors, or skipped when it catches XCTSkip.
and
Tip
You can include test assertions in a test case's setUp(), setUpWithError(), tearDown(), and tearDownWithError() instance methods. Any such assertions will be evaluated as part of every test method's run.
It isn't clear what you tried from your question. Do you have an assertion in setUp as suggested in the second option? Note that this only applies to the non-class setUp

Debugging window doesn't show print statements

I've been able to see print statements in the debugging window for my app. When I create a 'mock up' program (small trial app) to learn about Swift testing, none of the print statements in my LifecycleTests.swift file under the FirstTests folder display in the debugging window.
import XCTest
class LifecycleTests: XCTestCase {
override class func setUp() {
// Put setup code here. This method is called before the invocation of each test method in the class.
print("In class setUp.")
// NSLog("In class setUp.")
}
override class func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
print("In class tearDown.")
// NSLog("In class tearDown")
}
override func setup() {
print("In setup.")
// NSLog("In setup")
}
override func tearDown() {
print("In tearDown.")
// NSLog("In tearDown.")
}
func testExample() {
print("Starting test.")
// NSLog("Starting test.")
addTearDownBlock {
print("In first tearDown block.")
// NSLog("In first tearDown block.")
}
// print("In middle of test.")
NSLog("In middle of test.")
addTearDownBlock {
print("In second tearDown block.")
// NSLog("In second teardown block.")
}
print("Finishing test.")
// NSLog("Finishing test.")
}
func testPerformanceExample() {
// This is an example of a performance test case.
self.measure {
// Put the code you want to measure the time of here.
}
}
}
You can only get console output from one target at a time. Your console output is set to come by default from your app target, not your test target.
If you just run a test containing a print statement, you won't see any debugger output:
That test has a print statement, but we ran it and nothing appeared in the console.
Okay, but now let's trick the console into seeing the input from the test target. To do so, put a breakpoint in the test:
We step over the print statement and it prints to the console, along with a lot of other useful info about the test:
The interesting thing is that once you've used this trick, you can take the breakpoint away. The test target continues to be the console source until you run the app itself again.
The trick is a weird one but it appears to be the only way. Apple actually acknowledges it in an allusive way in their docs where they say:
if you have been actively engaged in debugging, any output from the debugging session also appears [in the console]
Evidently "actively engaged in debugging" means you've put a breakpoint in the test.

Is it possible to hide classes from ui tests

I have a few helper classes like UnlockedTestCase that configure my app for special scenario tests.
Theses classes show up in the test navigator without tests.
Is there a way to mark then as "not test classes" in order for them to be ignored by the test navigator?
UPDATE: Sample code:
class UnlockedTestCase: XCTestCase {
var app = XCUIApplication()
override func setUp() {
super.setUp()
continueAfterFailure = false
app.launchArguments = ["uiTesting", "unlock"]
app.launch()
}
}
A test would then be written as:
class UnlockedUITests: UnlockedTestCase {
func testButton() {
XCTAssers(app.buttons["SomeButtonInTheUnlockedState"].exists)
}
}
No, there is not a way to exclude that kind of class from the test navigator without losing the ease of defining the setUp(), as the way that it discovers test case classes is simplistic, and from the point of view of the navigator, you could add test cases to the parent/helper class at any moment since it is an XCTestCase descendant.
There is no protocol for 'helper' or 'abstract-only' classes which support inheritance in the way that you require, since inheritance from XCTestCase is required for the automatic discovery and usage of tests and test hooks like setUp().
If you really want to get rid of your helper entities from the test navigator, you could abstract them into protocols with extensions where Self: XCTestCase (to allow you access to XCTestCase's interface in the extension), have your test class conform to them, and override setUp() in the class where your tests are.
protocol UnlockableTest {}
extension UnlockableTest where Self: XCTestCase {
func unlockSetUp() {
continueAfterFailure = false
app.launchArguments = ["uiTesting", "unlock"]
app.launch()
}
}
class UnlockedUITests: XCTestCase, UnlockableTest {
var app = XCUIApplication()
override func setUp() {
super.setUp()
unlockSetUp()
}
func testButton() {
XCTAssert(app.buttons["SomeButtonInTheUnlockedState"].exists)
}
}
However, I think the simplicity and convenience of your current approach is the preferable compromise. Protocols also can't contain stored properties though, so in order to get the benefit of a stored property, you'd need to add the app property to every XCTestCase descendant too.
You can do this with Swift packages now, if your tests are in a Swift Package then you can have a helper module that contains your base classes and these will not be added to the sidebar as empty test cases.
You can mix an xcodeproj with a local to the source folder swift package to facilitate this, and in general I would personally recommend this as it improves build times and allows you to make your codebase more modular with granular access control.
Assuming the following structure:
MyTestHelperClassName: XCTestCase {
//... your helper methods
}
Remove its subclass declaration of XCTestCase
i.e the : XCTestCase part
Then it will not appear in the "Test Navigator"

Xcode UI test - swipeRight() not working after tearDown

I have a logout function that gets called during every tearDown(), but does not work when called this way. If I call the same logout function during the test, it works fine. I'm wondering what are the behaviors of XCUI testing during teardown, are there limitations? I tried debugging and calling app.swipeRight() using the lldb (espression->write code)...
-------
Navbar.swift
-------
import XCTest
import Foundation
class NavbarTest: XCTestCaseLib{
override func setUp()
{
super.setUp()
continueAfterFailure = false
}
override func tearDown()
{
logout()
super.tearDown()
}
func testSideBar_STAGING(){
//...<test code that executes no problem>
//...
}
}
-----
XCTestCaseLib.swift
------
import XCTest
import Foundation
class XCTestCaseLib: XCTestCase {
let app = XCUIApplication()
func logout() {
app.swipeRight()
...
}
From the code you've posted, it appears to be your imports (I'm assuming here that these classes are in different files, otherwise your inheritance is ambiguous). If I'm mistaken please update your question to include your file structure. Play around with your imports and inheritance.
I believe you just need to import XCTest on your NavbarTest class

Swift: overriding an initializer that takes an NSInvocation

I'm trying to create a reusable test harness in Swift with the idea that subclasses will extend the test harness to provide the instance under test, and can add their own subclass-specific test methods, something like this:
class FooTestHarness: XCTestCase {
let instance: Foo
init(instance: Foo) {
self.instance = instance
}
func testFooBehavior() {
XCTAssert(instance.doesFoo())
}
}
class FooPrime: Foo {
func doesFooPrime(): Bool { /* ... */ }
}
class FooPrimeTests: XCTestCase {
init() {
super.init(FooPrime())
}
func myInstance(): FooPrime {
return instance as FooPrime
}
func testFooPrimeBehavior() {
XCTAssert(myInstance().doesFooPrime())
}
}
However, when XCode's testrunner tries to run FooPrimeTests, it doesn't call the no-arg init(), it calls init(invocation: NSInvocation!) (and fails because there isn't one). I tried to override this in FooTestHarness:
init(invocation: NSInvocation!, instance: Foo) {
self.instance = instance
super.init(invocation)
}
and in FooPrimeTests:
init(invocation: NSInvocation!) {
super.init(invocation, FooPrime())
}
but this fails with the message 'NSInvocation' is unavailable.
Is there a workaround?
I'm not os sure if I got it right, but checking the code you suggested you should get a compiler Error like:
Which actually I reckon is quite normal since your FooPrimeTests is just subclassing XCTestCase which has got different init like:
init!(invocation: NSInvocation!)
init!(selector: Selector)
init()
Probably when you posted you're question you're running on an older version of Swift, (I'm currently running it on the Xcode Beta 6.2) that's why you can't see the error. But, and I say again if I got your point right, your class FooPrimeTests can't see you custom initializer just because is sublcassing XCTestCase, rather then FooTestHarness. Which is the class where the init(instance: Foo) is defined.
So you might probably want to define FooPrimeTests as subclass of FooTestHarness. That way you should be able to correctly see your initializer. Hope this help.