All Projects → jonreid → ViewControllerPresentationSpy

jonreid / ViewControllerPresentationSpy

Licence: MIT license
Unit test presented and dismissed iOS view controllers, including alerts and action sheets

Programming Languages

swift
15916 projects
objective c
16641 projects - #2 most used programming language
shell
77523 projects
ruby
36898 projects - #4 most used programming language

Projects that are alternatives of or similar to ViewControllerPresentationSpy

Conbini
Publishers, operators, and subscribers to supplement Combine.
Stars: ✭ 109 (-6.84%)
Mutual labels:  tvos, xctest
Entwine
Testing tools and utilities for Apple's Combine framework.
Stars: ✭ 306 (+161.54%)
Mutual labels:  tvos, xctest
SDWebImageSVGKitPlugin
A SDWebImage plugin to support SVG with SVGKit and category
Stars: ✭ 15 (-87.18%)
Mutual labels:  tvos
Dots
Lightweight Concurrent Networking Framework
Stars: ✭ 35 (-70.09%)
Mutual labels:  tvos
data-field
A SwiftUI view that wraps a text field to only accept specific data.
Stars: ✭ 13 (-88.89%)
Mutual labels:  tvos
GoogleMapsTileOverlay
GoogleMapsTileOverlay lets you customize Apple Maps MKMapView with the Google Maps StylingWizard
Stars: ✭ 68 (-41.88%)
Mutual labels:  tvos
Peasy
A pure Swift mock server for embedding and running directly within iOS/macOS UI tests. Easy peasy.
Stars: ✭ 32 (-72.65%)
Mutual labels:  xctest
SwiftKit
SwiftKit adds extra functionality to the Swift programming language.
Stars: ✭ 47 (-59.83%)
Mutual labels:  tvos
TVKit
UI components for tvOS
Stars: ✭ 20 (-82.91%)
Mutual labels:  tvos
Tesla-API
A iOS, macOS, watchOS and tvOS framework written in Swift to communicate with Teslas vehicle API
Stars: ✭ 32 (-72.65%)
Mutual labels:  tvos
IrregularGradient
Create animated irregular gradients in SwiftUI.
Stars: ✭ 127 (+8.55%)
Mutual labels:  tvos
appium-mac2-driver
Next-gen Appium macOS driver, backed by Apple XCTest
Stars: ✭ 55 (-52.99%)
Mutual labels:  xctest
OpenAPI-Swift
KKBOX Open API Swift Developer SDK for iOS/macOS/watchOS/tvOS
Stars: ✭ 13 (-88.89%)
Mutual labels:  tvos
AirPlayAuth
Since tvOS 10.2 AppleTV is enforcing the "Device verification" for AirPlay, which could be manually enabled/disabled before. This library allows to pair with an AppleTV and can be used in any app supporting streaming/casting to an AppleTV.
Stars: ✭ 82 (-29.91%)
Mutual labels:  tvos
SwiftCurrent
A library for managing complex workflows in Swift
Stars: ✭ 286 (+144.44%)
Mutual labels:  tvos
lisk-swift
Swift 4 library for Lisk - Including Local Signing for maximum security
Stars: ✭ 13 (-88.89%)
Mutual labels:  tvos
SDWebImagePhotosPlugin
A SDWebImage plugin to support Photos framework image loading
Stars: ✭ 47 (-59.83%)
Mutual labels:  tvos
NPAudioStream
Continuously stream a playlist of audio through a lightweight Objective-C library.
Stars: ✭ 23 (-80.34%)
Mutual labels:  tvos
QuoteKit
A framework to use the free APIs provided by https://quotable.io
Stars: ✭ 17 (-85.47%)
Mutual labels:  tvos
stinsen
Coordinators in SwiftUI. Simple, powerful and elegant.
Stars: ✭ 563 (+381.2%)
Mutual labels:  tvos

ViewControllerPresentationSpy

Build Status Carthage compatible CocoaPods Version Twitter Follow

ViewControllerPresentationSpy has three verifiers:

  • AlertVerifier to capture alerts and action sheets
  • PresentationVerifier to capture presented view controllers
  • DismissalVerifier to capture dismissed view controllers

Segues can be captured. Nothing is actually presented or dismissed. This means:

  • The workflow doesn't pause for an alert action to be selected.
  • Tests are blazing fast.
  • You can test things with unit tests instead of UI tests.

For concrete examples, see iOS Unit Testing by Example: chapter 9 "Testing Alerts," and chapter 10 "Testing Navigation Between Screens."

Contents

Writing Tests

What do I need to change in production code?

Nothing.

How do I test an alert controller?

  1. Instantiate an AlertVerifier before the Act phase of the test.
  2. Invoke the code to create and present your alert or action sheet.

Information about the alert or action sheet is then available through the AlertVerifier.

For example, here's a test verifying:

  • That one alert was presented, with animation.
  • That the presenting view controller was the System Under Test.
  • The alert title.
  • The alert message.
  • The preferred style of UIAlertController.Style.
  • The titles and styles of each action.

sut is the System Under Test in the test fixture. The Swift version uses a handy verify method.

func test_showAlert_alertShouldHaveTitle() {
    let alertVerifier = AlertVerifier()

    sut.showAlert() // Whatever triggers the alert

    alertVerifier.verify(
        title: "Hello!",
        message: "How are you?",
        animated: true,
        presentingViewController: sut,
        actions: [
            .default("OK"),
            .cancel("Cancel"),
        ]
    )
}
- (void)test_showAlert_alertShouldHaveTitle
{
    QCOAlertVerifier *alertVerifier = [[QCOAlertVerifier alloc] init];

    [sut showAlert]; // Whatever triggers the alert

    XCTAssertEqual(alertVerifier.presentedCount, 1, @"presented count");
    XCTAssertEqualObjects(alertVerifier.title, @"Hello!", @"title");
    XCTAssertEqualObjects(alertVerifier.message, @"How are you?", @"message");
    XCTAssertEqual(alertVerifier.animated, YES, @"animated");
    XCTAssertEqual(alertVerifier.preferredStyle, UIAlertController.Style.alert, @"preferred style");
    XCTAssertEqual(alertVerifier.presentingViewController, sut, @"presenting view controller");
    XCTAssertEqual(alertVerifier.actions.count, 2, @"actions count);
    XCTAssertEqualObjects(alertVerifier.actions[0].title, @"OK", @"first action");
    XCTAssertEqual(alertVerifier.actions[0].style, UIAlertActionStyleDefault, @"first action");
    XCTAssertEqualObjects(alertVerifier.actions[1].title, @"Cancel", @"second action");
    XCTAssertEqual(alertVerifier.actions[1].style, UIAlertActionStyleCancel, @"second action");
}

How can I invoke the closure associated with a UIAlertAction?

Go through the steps above to present your alert or action sheet. Then call executeAction (forButton:) on your AlertVerifier with the button title. For example:

func test_executingActionForOKButton_shouldDoSomething() throws {
    let alertVerifier = AlertVerifier()
    sut.showAlert()
    
    try alertVerifier.executeAction(forButton: "OK")

    // Now assert what you want
}
- (void)test_executingActionForOKButton_shouldDoSomething
{
    QCOAlertVerifier *alertVerifier = [[QCOAlertVerifier alloc] init];
    [sut showAlert];

    NSError *error = nil;
    [alertVerifier executeActionForButton:@"OK" andReturnError:&error];

    XCTAssertNil(error);
    // Now add your own assertions
}

Because this method can throw an exception, declare the Swift test method as throws and call the method with try. For Objective-C, pass in an NSError and check that it's not nil.

How do I test a presented view controller?

  1. Instantiate a PresentationVerifier before the Act phase of the test.
  2. Invoke the code to create and present your view controller.

Information about the presentation is then available through the PresentationVerifier.

For example, here's a test verifying:

  • That one view controller was presented, with animation.
  • That the presenting view controller was the System Under Test.
  • That the type of the presented view controller is correct.
  • That the presented view controller has a particular property.

sut is the System Under Test in the test fixture. The Swift version uses a handy verify method.

func test_presentedVC_shouldHaveSpecialSettingHello() {
    let presentationVerifier = PresentationVerifier()

    sut.showVC() // Whatever presents the view controller

    let nextVC: MyViewController? = presentationVerifier.verify(animated: true,
                                                                presentingViewController: sut)
    XCTAssertEqual(nextVC?.specialSetting, "Hello!")
}
- (void) test_presentedVC_shouldHaveSpecialSettingHello
{
    QCOPresentationVerifier *presentationVerifier = [[QCOPresentationVerifier alloc] init];

    [sut showVC]; // Whatever presents the view controller

    XCTAssertEqual(presentationVerifier.presentedCount, 1, @"presented count");
    XCTAssertTrue(presentationVerifier.animated, @"animated");
    XCTAssertEqual(presentationVerifier.presentingViewController, sut, @"presenting view controller");
    if (![presentationVerifier.presentedViewController isKindOfClass:[MyViewController class]])
    {
        XCTFail(@"Expected MyViewController, but was %@", presentationVerifier.presentedViewController);
        return;
    }
    MyViewController *nextVC = presentationVerifier.presentedViewController;
    XCTAssertEqualObjects(nextVC.specialSetting, @"Hello!");
}

How do I test a segue?

It depends. First, follow the steps above for testing a presented view controller. Trigger the segue from test code. For example, we can trigger a segue attached to a button by calling sendActions(for: .touchUpInside) on the button.

Segue Type: Present Modally

That's all you need to do. But you need to be aware of a memory issue:

Neither the presenting view controller nor the presented view controller will be deallocated during test execution. This can cause problems during test runs if either affects global state, such as listening to the NotificationCenter. You may need to add special methods outside of deinit that allow tests to clean them up.

Segue Type: Show

A "Show" segue (which does push navigation) takes a little more work.

First, install the presenting view controller as the root view controller of a UIWindow. Make this window visible.

let window = UIWindow()
window.rootViewController = sut
window.isHidden = false

To clean up memory at the end, add this to the beginning of the tearDown() method of the test suite to pump the run loop:

RunLoop.current.run(until: Date())

This ensures that the window is deallocated at the end of the test case. That way, both the view controllers will also cease to exist.

How do I test dismissing a modal?

  1. Instantiate a DismissalVerifier before the Act phase of the test.
  2. Invoke the code to dismiss your modal.

Information about the dismissal is then available through the DismissalVerifier.

For example, here's a test verifying that a particular view controller was dismissed, with animation.

sut is the System Under Test in the test fixture. The Swift version uses a handy verify method.

func test_dismissingVC() {
    let dismissalVerifier = DismissalVerifier()

    sut.dismissVC() // Whatever dismisses the view controller

    dismissalVerifier.verify(animated: true, dismissedViewController: sut)
}
- (void) test_dismissingVC
{
    QCODismissalVerifier *dismissalVerifier = [[QCODismissalVerifier alloc] init];

    [sut dismissVC]; // Whatever dismisses the view controller

    XCTAssertEqual(dismissalVerifier.dismissedCount, 1, @"dismissed count");
    XCTAssertTrue(dismissalVerifier.animated, @"animated");
    XCTAssertEqual(dismissalVerifier.presentingViewController, sut, @"dismissed view controller");
}

How can I invoke the closure passed to present or dismiss?

The production code completion handler is captured in the verifier's capturedCompletion property.

How can I test something that's presented or dismissed using DispatchQueue.main?

Create an expectation in your test case. Fulfill it in the verifier's testCompletion closure. Add a short wait at the start of the Assert phase.

func test_showAlertOnMainDispatchQueue_shouldDoSomething() {
    let alertVerifier = AlertVerifier()
    let expectation = self.expectation(description: "alert presented")
    alertVerifier.testCompletion = { expectation.fulfill() }
    
    sut.showAlert()
    
    waitForExpectations(timeout: 0.001)
    // Now assert what you want
}
func test_presentViewControllerOnMainDispatchQueue_shouldDoSomething() {
    let presentationVerifier = PresentationVerifier()
    let expectation = self.expectation(description: "view controller presented")
    presentationVerifier.testCompletion = { expectation.fulfill() }
    
    sut.showVC()
    
    waitForExpectations(timeout: 0.001)
    // Now assert what you want
}

Can I see some examples?

There are sample apps in both Swift and Objective-C. Run them on both phone & pad to see what they do, then read the ViewControllerAlertTests and ViewControllerPresentationTests.

How Do I Add ViewControllerPresentationSpy to My Project?

Swift Package Manager

Include a ViewControllerPresentationSpy package in your Package.swift manifest's array of dependencies:

dependencies: [
    .package(
        url: "https://github.com/jonreid/ViewControllerPresentationSpy",
        .upToNextMajor(from: "6.0.0")
    ),
],

CocoaPods

Add the following to your Podfile, changing "MyTests" to the name of your test target:

target 'MyTests' do
  inherit! :search_paths
  pod 'ViewControllerPresentationSpy', '~> 6.0'
end

Carthage

Add the following to your Cartfile:

github "jonreid/ViewControllerPresentationSpy" ~> 6.0

Prebuilt Framework

A prebuilt binary is available on GitHub. The binary is packaged as ViewControllerPresentationSpy.xcframework, containing these architectures:

  • Mac Catalyst
  • iOS device
  • iOS simulator
  • tvOS device
  • tvOS simulator

Drag the XCFramework into your project.

Build Your Own

If you want to build ViewControllerPresentationSpy yourself, clone the repo, then

$ cd Source
$ ./MakeDistribution.sh
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].