4

A pretty common problem with any kind of integration test is getting the unit under test into a known state -- the state that sets up well for the test you want to perform. With a unit test, there's usually not much state, and the only issue is in potentially mocking out interactions with other classes.

On the other hand, when testing a whole app there's all sorts of potentially persistent state, and getting the app into a clean state, or trickier still, into a known state that isn't "clean" without any access to the app itself is a little tricky.

The only suggestion I've found is to embed any necessary setup in the app, and use something like an environment variable to trigger setup. That is, of course, viable, but it's not ideal. I don't really want to embed test code and test data in my final application if I can avoid it.

And then there's mocking out interactions with remote services. Again you can embed code (or even a framework) to do that, and trigger it with an environment variable, but again I don't love the idea of embedding stubbing code into the final app.

Suggestions? I haven't been able to find much, which makes me wonder if no-one is using Xcode UI testing, or is only using it for incredibly simple apps that don't have these kinds of issues.

Geoffrey Wiseman
  • 5,459
  • 3
  • 34
  • 52

2 Answers2

2

Unfortunately, the two suggestions you mentioned are the only ones that are possible with Xcode UI Testing in its current state.

There is, however, one thing you can do to mitigate the risk of embedding test code in your production app. With the help of a few compiler flags you can ensure the specific code is only built when running on the simulator.

#if (arch(i386) || arch(x86_64)) && os(iOS)
    class SeededHTTPClient: HTTPClientProtocol {
        /// ... //
    }
#endif

I'm in the middle of building something to make this a little easier. I'll report back when its ready for use.

Community
  • 1
  • 1
Joe Masilotti
  • 16,815
  • 6
  • 77
  • 87
2

Regarding setting up the state on the target app there's a solution. Both the test runner app and your app can read and write to the simulator /Library/Caches folder. Knowing that you can bundle fixture data in your test bundle, copy it to the /Library/Caches on setUp() and pass a launch argument to your application to use that fixture data.

This only requires minimal changes to your app. You only need to prepare it to handle this argument at startup and copy over everything to your app container.

If you want to read more about this, or how you can do the same when running on the device, I've actually written a post on it.

Regarding isolating your UI tests from the network, I think the best solution is to embed a web server on your test bundle and have your app connect to it (again you can use a launch argument parameterize your app). You can use Embassy for that.

pfandrade
  • 2,359
  • 15
  • 25