ViewControllerPresentationSpy has three verifiers:
AlertVerifier
to capture alerts and action sheetsPresentationVerifier
to capture presented view controllersDismissalVerifier
to capture dismissed view controllersSegues can be captured. Nothing is actually presented or dismissed. This means:
For concrete examples, see iOS Unit Testing by Example: chapter 9 "Testing Alerts," and chapter 10 "Testing Navigation Between Screens."
Nothing.
AlertVerifier
before the Act phase of the test.Information about the alert or action sheet is then available through the AlertVerifier.
For example, here's a test verifying:
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");
}
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.
PresentationVerifier
before the Act phase of the test.Information about the presentation is then available through the PresentationVerifier.
For example, here's a test verifying:
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!");
}
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.
DismissalVerifier
before the Act phase of the test.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");
}
The production code completion handler is captured in the verifier's capturedCompletion
property.
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
}
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.
Include a ViewControllerPresentationSpy package in your Package.swift manifest's array of dependencies:
dependencies: [
.package(
url: "https://github.com/jonreid/ViewControllerPresentationSpy",
.upToNextMajor(from: "7.0.0")
),
],
Add the following to your Podfile, changing "MyTests" to the name of your test target:
target 'MyTests' do
inherit! :search_paths
pod 'ViewControllerPresentationSpy', '~> 7.0'
end
Add the following to your Cartfile:
github "jonreid/ViewControllerPresentationSpy" ~> 7.0
A prebuilt binary is available on GitHub. The binary is packaged as ViewControllerPresentationSpy.xcframework, containing these architectures:
Drag the XCFramework into your project.
If you want to build ViewControllerPresentationSpy yourself, clone the repo, then
$ cd Source
$ ./MakeDistribution.sh
link |
Stars: 146 |
Last commit: 5 weeks ago |
07 Jan 2023
Adds @MainActor
annotations to spies for Xcode 14 fix. You will need to add @MainActor
to any
test suites that use these spies.
Swiftpack is being maintained by Petr Pavlik | @ptrpavlik | @swiftpackco | API | Analytics