2

What's a good alternative to setting XCUIDevice.shared.orientation for changing the orientation during XCTests in Xcode 13?

Many of our app's existing snapshot tests set the device orientation using:

XCUIDevice.shared.orientation = orientation.deviceOrientation

However in Xcode 13, these tests fail due to the following exception being thrown when this method is called:

Failed to set device orientation: Not authorized for performing UI testing actions. Exception _XCTestCaseInterruptionException * 0x60000082b060 0x000060000082b060

Googling this error led me to find this Flutter issue where they state:

I guess Xcode 13 will limit the use of XCUIDevice to XCUITests.

Our snapshot tests are not XCUITests, though, so what options do we have to force a particular device rotation so that we can snapshot the way the view would look in landscape and portrait?

scaly
  • 509
  • 8
  • 18
  • Sorry but this makes no sense. How can your tests that take snapshots not be UI tests? Is it because of something about Flutter? (Sorry, I know nothing about Flutter.) – matt Sep 17 '21 at 19:26
  • We don't use Flutter. I only mention Flutter because it came up in Google when I searched for "XCUIDevice.shared.orientation = orientation.deviceOrientation". Our tests that take snapshots are not XCUITests, they are just XCTests with a host application using FBSnapshotTestCase framework. XCUITests don't let you `@testable import` anything from the app, so it's not useful for snapshot tests where we need to configure certain classes with mock models etc. – scaly Sep 17 '21 at 20:34
  • Well, that is not true. I mean yes, it's true that you can't do a testable import; you can't see into the code from a UI test. But a UI test can inject environment variables into the app, where the app itself can configure itself in a special way for testing. See for example https://stackoverflow.com/questions/48199055/xcode-ui-test-environment-variables-not-being-passed-from-scheme on how to do that. Basically I would take issue with the entire premise of your question; you should be doing UI testing for snapshots. — I've given that as an answer. – matt Sep 17 '21 at 21:24

3 Answers3

1

This solution from @Dmytro on another question works for me, use:

UIDevice.current.setValue(
    NSNumber(integerLiteral: orientation.deviceOrientation.rawValue), 
    forKey: "orientation"
)

instead of:

XCUIDevice.shared.orientation = orientation.deviceOrientation

and now it works fine.

Note: .deviceOrientation refers to our custom extension on UIInterfaceOrientation:

public extension UIInterfaceOrientation {
    var deviceOrientation: UIDeviceOrientation {
        switch self {
        case .landscapeLeft:
            return .landscapeRight
        case .landscapeRight:
            return .landscapeLeft
        case .portrait:
            return .portrait
        case .portraitUpsideDown:
            return .portraitUpsideDown
        case .unknown:
            return .unknown
        }
    }
}
pkamb
  • 33,281
  • 23
  • 160
  • 191
scaly
  • 509
  • 8
  • 18
0

I would take issue with the entire premise of your question; you should be doing UI testing for snapshots, not unit testing. UI testing has very powerful support for snapshotting as well as for dropping a lot of other information into the report for later retrieval.

It's true that a UI test can't reach into an app's code and make it behave. But to make the app behave in special ways during testing, a UI test can inject environment variables into the app, where the app itself can configure itself in a special way for testing. See for example Xcode UI Test environment variables not being passed from Scheme on how to do that.

matt
  • 515,959
  • 87
  • 875
  • 1,141
0

We faced the same problem in our project with snapshot tests. Unfortunately, UIDevice.current.setValue didn't do the trick for us.

So, we decided to go with this solution:

  1. add a new dummy UITest case file (not unit test case!):

enter image description here

  1. name it starting with an 'A' so it would be the first in alphabetical order (and thus will be executed before other tests)

  2. override class setUp() method with your code for changing device orientation. in our case it is changing it to portrait like so:

    override class func setUp() {
        super.setUp()
        if XCUIDevice.shared.orientation != .portrait {
            XCUIDevice.shared.orientation = .portrait
        }
    }
    
  1. add a dummy test, so setUp() will be executed:

    func testDummyTest() {
        XCTAssert(true)
    }
    

And that's it. Of course, it will not work when we trigger only one test and not a whole bundle, but as a workaround it can do. Hopefully, it will be fixed in future Xcode releases.

Here is the full code:

import XCTest

/// The class name should be the first in alphabetical order
class AUITestsSetUp: XCTestCase {

    override class func setUp() {
        super.setUp()
        if XCUIDevice.shared.orientation != .portrait {
            XCUIDevice.shared.orientation = .portrait
        }
    }

    func testDummyTest() {
        XCTAssert(true)
    }
}
joliejuly
  • 2,127
  • 1
  • 21
  • 24