How to detect if iOS app is running in UI Testing mode - ios

I would like my app to run special code (e.g. resetting its state) when running in UI Testing mode. I looked at environment variables that are set when the app is running from UI Testing and there aren't any obvious parameters to differentiate between the app running normally vs in UI Testing. Is there a way to find out?
Two workarounds that I'm not satisfied with are:
Set XCUIApplication.launchEnvironment with some variable that I later check in the app. This isn't good because you have to set it in the setUp method of each test file. I tried setting the environment variable from the scheme settings but that doesn't propagate to the app itself when running UI Testing tests.
Check for the lack of existence of the environment variable __XPC_DYLD_LIBRARY_PATH. This seems very hacky and might only be working now because of a coincidence in how we have our target build settings set up.

I've been researching this myself and came across this question. I ended up going with #LironYahdav's first workaround:
In your UI test:
- (void)setUp
{
[super setUp];
XCUIApplication *app = [[XCUIApplication alloc] init];
app.launchEnvironment = #{#"isUITest": #YES};
[app launch];
}
In your app:
NSDictionary *environment = [[NSProcessInfo processInfo] environment];
if (environment[#"isUITest"]) {
// Running in a UI test
}
#JoeMasilotti's solutions are useful for unit tests, because they share the same runtime as the app being tested, but are not relevant for UI tests.

I didn't succeed with setting a launch environment, but got it to work with launch arguments.
In your tests setUp() function add:
let app = XCUIApplication()
app.launchArguments = ["testMode"]
app.launch()
In your production code add a check like:
let testMode = NSProcessInfo.processInfo().arguments.contains("testMode")
if testMode {
// Do stuff
}
Verified using Xcode 7.1.1.

You can use Preprocessor Macros for this. I found that you have couple of choices:
New Target
Make a copy of the App's target and use this as the Target to be Tested. Any preproocessor macro in this target copy is accessible in code.
One drawback is you will have to add new classes / resources to the copy target as well and sometimes it very easy to forget.
New Build Configuration
Make a duplicate of the Debug build configuration , set any preprocessor macro to this configuration and use it for your test (See screenshots below).
A minor gotcha: whenever you want to record a UI Testing session you need to change the Run to use the new testing configuration.
Add a duplicate configuration:
Use it for your Test:

Swift 3 based on previous answers.
class YourApplicationUITests: 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 = false
// UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method.
let app = XCUIApplication()
app.launchArguments = ["testMode"]
app.launch()
// 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() {
// Use recording to get started writing UI tests.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
}
extension UIApplication {
public static var isRunningTest: Bool {
return ProcessInfo().arguments.contains("testMode")
}
}
Then just call UIApplication.isRunningTest in your code.

I've just added this extension
#available(iOS 9, *)
extension XCUIApplication {
func test(){
launchEnvironment = ["TEST":"true"]
launch()
}
}
So I can just use test() instead of launch()

In Swift 3 you can check for the XCInjectBundleInto key, or something that starts with XC.
let isInTestMode = ProcessInfo.processInfo.environment["XCInjectBundleInto"] != nil
This works in OS X as well.

My solution is almost identical to that of Ciryon above, except that for my macOS Document-based app I had to prepend a hyphen to the argument name:
let app = XCUIApplication()
app.launchArguments.append("-Testing")
app.launch()
...otherwise, "Testing" ends up interpreted as the name of the document to open when launching the app, so I was getting an error alert:

Related

Mock iOS App Version Number in Unit Test

I have added functionality to my project that downloads JSON and compares the version numbers in there with the currently installed app version to determine whether a feature should be enabled or not. However, I am now trying to unit test this and I am not sure how to mock the current app version.
Can I inject a value into the info.plist in a test?
Can I completely mock the info.plist in a test?
Or should I:
Add a function in my class to retrieve the version number from the info.plist file and then mock that function?
On app startup, store the version number in NSUserDefaults and the mock this?
I would definitely go with the function which retrieves version number. This way you can get it from info.plist in production code and mock whatever you want in tests. Additionally you will be able to test the retrieval of app version as well :)
Or even better, create another class which gets the application number and inject instance to the class which downloads JSONs. You'll be then able to mock this however you want.
protocol AppVersionProvider {
func getAppVersion() -> String
}
class JSONDownloader {
private let appVersionProvider: AppVersionProvider
public init(appVersionProvider: AppVersionProvider) {
self.appVersionProvider = appVersionProvider
}
public func downloadJSON() {
if appVersionProvider.getAppVersion() != networkingCallResult.appVersion {
...
}
}
}
There, you can mock AppVersionProvider protocol in test with some stub and use info.plist provider for production.

Know at runtime that you are running in IBDesignable

(programming in swift 2)
In my iOS application I am using frameworks. I am also using the IBDesignable feature with great success. I have a bunch of views and buttons in frameworks that are IBDesignable and thus render nicely on the screen of interface builder.
Problem is that one of these views executes code when its initialized that will do an assertion if its being run just in the context of the IBDesignable interfacebuilder (executed just to render the IB screen). The reason for the assertion is valid, and really does not matter, bottom line is that I do not want to loose this functionality during "normal" operation.
My idea for a solution is to know when the code is being executed just for rendering in IB for IBDesignable mode, and thus build in a switch to not/do the assert.
I tried using the #if !TARGET_INTERFACE_BUILDER but this is not working, as this is a preprocessor directive and thus only evaluated a compile time, and the frameworks are precompiled (when used in the actual app).
I also thought about using prepareForInterfaceBuilder() but this is not applicable because the class throwing the assert has nothing todo with UIView itself.
Is there a function or any other way to check AT RUNTIME (in a framework) that your code is being run as part of IB screen rendering for IBDesignable mode?
After testing a few dozens solutions, I found the following to work reliably:
/**
Returns true if the code is being executed as part of the Interface Builder IBDesignable rendering,
so not for testing the app but just for rendering the controls in the IB window. You can use this
to do special processing in this case.
*/
public func runningInInterfaceBuilder() -> Bool {
//get the mainbundles id
guard let bundleId = NSBundle.mainBundle().bundleIdentifier else {
//if we don't have a bundle id we are assuming we are running under IBDesignable mode
return true
}
//when running under xCode/IBDesignable the bundle is something like
//com.apple.InterfaceBuilder.IBCocoaTouchPlugin.IBCocoaTouchTool
//so we check for the com.apple. to see if we are running in IBDesignable rendering mode
//if you are making an app for apple itself this would not work, but we can live with that :)
return bundleId.rangeOfString("com.apple.") != nil
}
SWIFT 4 version
/**
Returns true if the code is being executed as part of the Interface Builder IBDesignable rendering,
so not for testing the app but just for rendering the controls in the IB window. You can use this
to do special processing in this case.
*/
public func runningInInterfaceBuilder() -> Bool {
//get the mainbundles id
guard let bundleId = Bundle.main.bundleIdentifier else {
//if we don't have a bundle id we are assuming we are running under IBDesignable mode
return true
}
//when running under xCode/IBDesignable the bundle is something like
//com.apple.InterfaceBuilder.IBCocoaTouchPlugin.IBCocoaTouchTool
//so we check for the com.apple. to see if we are running in IBDesignable rendering mode
//if you are making an app for apple itself this would not work, but we can live with that :)
return bundleId.contains("com.apple.")
}

Defining global variables according to whether or not Xcode UI tests are running

How can I define global variables according to whether or not Xcode UI Tests are running? I'm trying to do this:
#if UITESTS
let api = StubbedAPI()
#else
let api = RealAPI()
#endif
These are global variables, so I can't call NSProcessInfo.processInfo().environment or NSProcessInfo.processInfo().arguments at file scope.
The UI Testing target runs as a separate process from your application. This means you can't set preprocessor macros in the test target and expect the app to know about them. The only way the tests can communicate with the app is via the two processInfo settings you mention.
Using these is dynamic, whereas your proposed solution is static. However, it is still possible to do what you are trying to do with the tools Apple has given us.
First, create a protocol that both StubbedAPI and RealAPI conform to.
protocol API {
// ... //
}
class RealAPI: API {
// ... //
}
class StubbedAPI: API {
// ... //
}
Next, create a configuration class. This will be used to tell your code which API to use at run time.
struct Config {
var api: APIProtocol {
get {
return UITesting() ? RealAPI() : StubbedAPI()
}
}
}
private func UITesting() -> Bool {
return NSProcessInfo.processInfo().arguments.contains("UI-TESTING")
}
Then, retrieve a reference to an implementation of API via the configuration.
class FooService {
private let api = Config().api;
}
Finally, set the processInfo argument before you launch the app under UI Testing.
class UITests: TestCase {
let app = XCUIApplication()
override func setUp() {
super.setUp()
app.launchArguments = ["UI-TESTING"]
app.launch()
}
}
The api property will be set to the real API when running production code and the stubbed one under UI Testing.
There are some downsides to this approach. First, you are introducing the actual "stubbed" API to your production code. This has the potential downside of a developer actually using this in production. Second, you are required to create the API protocol to only have one "real" object implement it. Unfortunately, this is the best solution I've come up with given the current state of UI Testing and Swift.

How can I set flags that my app will see when it's running under XCTest?

KIF works like magic, something puzzled me a lot recently. That is how exactly KIF and XCTest works.
It is said in WWDC 2014 & 2013: Test code is injected into the host application as kind of a plugin, so what information can be eavesdrop from main app as a plugin? What is the relationship between Host Application & Plugin??
Using KIF, we can sense the Notifications & URL request, but how that works? My first thought is all context in host application is shared with plugin But why in test, we cannot modify variables in the main app?
For example:
we have two targets:
Magic Project
-- MagicApp
-- MagicUITest <- KIFTest target
Suppose I have a file called MagicClass:
class MagicClass {
static var a = 1
}
Is that possible if I want to modify that variable in MagicUITest?
The problem I faced right now is how can I inject different UIViewController to RootViewController so that I can run KIF test from each individual ViewController and don't need bother going through the login process all the time .. my guess is something like:
in MagicApp:
class AppDelegate : .. , .. {
func application(application: UIApplication, willFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
if MagicClass.a == 1 {
window.rootViewController = AViewController()
} else {
window.rootViewController = BViewController()
}
}
}
in KIFTest's setUp() :
setUp() {
Magic.a = 2
}
But not work ..
One thing is Magic.a = 2 won't change the Magic.a in MagicApp, second is application(..,..,..) function will end before setUp() is called ..
Any idea ?
While you're correct that the exact code you've tried won't work, you'll be able to use a very similar approach.
This code, for example, is cribbed directly from the app I'm working on right now in a class called RootViewController that's (you guessed it) the app's root view controller:
public override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
if NSProcessInfo.processInfo().environment["TESTING"] == nil {
loadChildren()
}
}
What it does is looks for an environment variable to decide if it should automatically continue with loading the app, and if it finds the environment variable it waits for the test stack to finish setting up and telling it what portion of the app to load next. (You could do this in your app delegate, too, if it's a really small app.)
You can set the environment variable by editing the scheme in Xcode:
All that said, though: take care when using this strategy. You want your tests to be testing features of your app as they exist in your app, and if showing different screens arbitrarily is a feature of your app, that's probably okay. But if you start writing code that makes your app behave differently when under test, you're not really testing your app anymore, are you?

Check if the running code is unit test case or not

I want to check if the running code is unit tese case or not execute different code for the result such as:
if ( unit test case is running )
{
do something
}
else
{
do other thing
}
any idea on this?
This is a bad approach you should try to simulate logic parts which you are trying to avoid by this statemetn through Mock ojects or other mechanism.
Now to your question you can use a oolean variable like isUnittest which you set on test setup and Teardown, ut as saied i dont recommend you doing this.
This seems to work for me (iOS 8, Xcode 6):
- (BOOL) isRunningTest {
return NSClassFromString(#"XCTestCase") != nil;
}
I think this is cleaner and easier than other answers.
Another way is to let the class have customizable behavior controlled via a static method, and have test case call that method in its static load method.
I had a similar issue using storyboard and an external restful service for authentication via oauth. The app delegate would check if there is a valid oauth token in appdelegate:didFinishLaunchingWithOptions, and if not, then programatically trigger segue to do the oauth login. But this was not desirable in test cases. To solve this, I created a static method within the app delegate to disable the login screen. This is the code within my app delegate:
static Boolean showLoginScreen = TRUE ;
+ (void) disableLoginScreen
{
showLoginScreen = FALSE ;
NSLog(#"disabled login screen") ;
}
The test case had its load method to do the following:
// disable login screen for the test case
+ (void) load {
NSLog( #"now disabling login screen" ) ;
[XYZAppDelegate disableLoginScreen];
}
This worked because the test case class was loaded before application was initialized. Of course you must check the value of this flag within app delegate to trigger/not trigger the login segue. Other alternatives I tried but rejected were as follows:
Create a preprocessor define on the test target. But the compiler only compiles the test case files with this flag, not the application source. See http://www.innovaptor.com/blog/2013/09/02/xcode-preprocessor-macros-for-test-code.html
Use the static initialize method of the test case to call the disable method. On test case run, the application is started before the test case class is loaded. See http://www.friday.com/bbum/2009/09/06/iniailize-can-be-executed-multiple-times-load-not-so-much/ for some details.
Don't message UIAlertView directly. Instead, use dependency injection, for example, a property like
#property (strong, nonatomic) Class alertViewClass;
Then your code to create an alert can do
UIAlertView *alert = [[_alertViewClass alloc] initWithTitle:…etc…];
In your test code, inject a different class. I use https://github.com/jonreid/JMRTestTools to specify JMRMockAlertView. I can then test the alert calls with JMRMockAlertViewVerifier. (In fact, this enables test-driven development of alerts.)
Edit: These days, I use https://github.com/jonreid/ViewControllerPresentationSpy

Resources