Mocking APIs and maintaining tests with the Page Object Model
Importance of UI Testing
Unit tests are important. They make sure our functions work properly, that we didn’t break anything, and to catch edge cases.
From the user’s perspective, however, it doesn’t matter if pressing the confirm button doesn’t lead them to the expected screen. So, UI testing doesn’t replace unit testing in your test suite, it just completes it.
Problems of UI Testing
I don’t know how many developers maintain unit testing over time, but I know that UI testing is still pretty rare among developers. For good reasons, actually.
UI testing can be hard to set up to keep from breaking up over time, due to the following reasons:
- Our app works with a back-end server, so tests become very inconsistent: network issues or server issues. We don’t have control over the requests returning from the server.
- Writing UI test scripts takes a lot of time. Locating buttons or handling navigation between screens for a single test. And then we have to duplicate the code when we want to create a new test case.
- Not only are the tests long to write, they are also not very readable. Try to understand them two days after their creation and you’ll know what I mean.
- UI is changing all the time. We add and remove buttons, screens, scrolls, and more. It becomes a nightmare updating tests every time we want to make a small change.
Simulate Your Back End
Most of the mobile apps work with a back-end server and for UI testing this can be a challenge.
The first rule of any automatic testing is that, if you run your tests 1000 times, you should expect the same results each time. So, server and network issues make your tests unreliable.
But, if you follow SoC (separation of concerns), you can simulate your back-end server by creating a mock server, implementing it in your network layer and making your test fully reliable and predictable.
Many tools can help you mock your server but the principles are always the same — replace your server with a local component that returns predictable and reliable response objects to your network requests.
To test your app in various cases (log-in, sign-out, data sync, etc.), you can save your “request->response” mock data to different JSON files where each file represent different scenarios (log-in, register).
You load your test with the preferred file using the launch options, so you’re not only solving the reliability issues, but you can also test your app with different cases.
But it takes time to build all the mocking data!
You are a developer, do your magic! “Record” requests and responses while running your app once. You can do that with Charles Proxy or write some code that saves all the network data to a file.
Mocking Other APIs
This method doesn’t have to end with only mocking your server. You can mock other APIs as well.
For example, let’s say you have an app that manages address book contacts and it syncs with the device’s address book.
Again, if you separated your address book connector from the rest of the app and followed protocol-oriented design patterns, you can replace your connector with a mock address book and return different contact’s objects, no matter what address book the device currently holds.
The same goes for calendars, reminders, and other system APIs.
Maintaining Tests With Page Object Model
Writing UI test scripts takes time and effort but the real problem is how to maintain it over time.
The simplest way to solve this problem is to create something called a Page Object Model.
A Page Object Model is an object attached to a screen. It has a reference to XCUIApplication and its responsibilities are to take actions on the page, locate elements and return the next page object if needed.
Note that I’m not planning on showing you how to write UI testing scripts but rather, I’d like to show you how to solve day-to-day problems that occur when setting up a good testing infrastructure for your app.
The principles of Page Objects is that we separate the test script from handling the layout.
This lets us quickly write very small, readable UI tests and, at the same time, reuse the hard work by building a dedicated object for each screen.
Also, when your UI changes, all you have to do is modify the relevant methods in your Page Object, without touching your actual test cases.
This was a short introduction on how to overcome issues with UI testing.
As I said, there are additional and more sophisticated tools, such as setting up your local server for mocking, Screenplay Pattern, or Xcode’s configuration files. I’ll try to get into those in future articles.
The most important thing is that you need to set up a stable, readable, and reusable UI testing infrastructure and stop wasting precious time by letting Xcode do the sanity checks for you.