Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
UI tests without UI Testing experiment

UI tests without UI Testing experiment

This gist is a small experiment to see if there's an "in-between" for testing iOS applications. More feature-level than XCTest but not as heavy handed (or slow and brittle) as UI Testing.

The general idea is to provide an API similar to UI Testing but be able to spin up any controller on the fly. By putting the controller inside of a window the test behaves a bit more like the real app.

Currently, only two methods are explored: finding labels and buttons. An obvious omission is searching for the view recursively. A perhaps less obvious omission is still being able to tap disabled buttons.

This extends on my thoughts in a recent blog, Testing the UI without UI Testing in Swift.

Is this crazy?

Probably! But maybe this is something worth exploring.

Have you tried anything like this? Is this obviously a maintenance nightmare? I would love to hear what you think.

import XCTest
struct App {
private weak var controller: UIViewController!
private let window = UIWindow()
mutating func loadController(withIdentifier identifier: String) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
controller = storyboard.instantiateViewController(identifier: identifier, creator: nil)
window.rootViewController = controller
func label(text: String) -> UILabel {
let label = controller.view.subviews.first { view -> Bool in
guard let label = view as? UILabel else { return false }
return label.text == text
} as? UILabel
if label == nil {
XCTFail("Could not find label with text '\(text)'.")
return label!
func button(title: String) -> UIButton {
let button = controller.view.subviews.first { view -> Bool in
guard let button = view as? UIButton else { return false }
return button.title(for: .normal) == title
} as? UIButton
if button == nil {
XCTFail("Could not find button with title '\(title)'.")
return button!
import UIKit
extension UIButton {
func tap() {
sendActions(for: .touchUpInside) Date())
import XCTest
class Tests: XCTestCase {
var app: App!
override func setUpWithError() throws {
app = App()
continueAfterFailure = false
func testTappingAButton() throws {
app.loadController(withIdentifier: "Home")
app.button(title: "Toggle text").tap()
XCTAssertFalse(app.label(text: "Some text").isHidden)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment