Top Banner
Testing view controllers with Quick and Nimble Marcio Klepacz iOS Engineering @ GetYourGuide - Swift Berlin 2015
26

Testing view controllers with Quick and Nimble

Apr 12, 2017

Download

Technology

Marcio Klepacz
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Testing view controllers with Quick and Nimble

Testing view controllers with Quick and Nimble

Marcio Klepacz iOS Engineering @ GetYourGuide - Swift Berlin 2015

Page 2: Testing view controllers with Quick and Nimble

Overview

• The Problem

• Tools

• Example

• Conclusion

Page 3: Testing view controllers with Quick and Nimble

The Problem

Page 4: Testing view controllers with Quick and Nimble

View Controllers

• The place where the user interface connects with the app logic and model

Controller

Model View

Page 5: Testing view controllers with Quick and Nimble

• One of the pillars of the architecture.

• Sensible code.

• Involuntary change can damage.

Page 6: Testing view controllers with Quick and Nimble

Testing View Controllers

• Not easy

• lifecycle managed by the framework.

• lot’s of states.

😰

Page 7: Testing view controllers with Quick and Nimble

Tools

Page 8: Testing view controllers with Quick and Nimble

Behaviour Driven Development (BDD)

BDD BDD+View Controllers

Tests that verify what an application does are

behavioural tests.

What a view controllers does rather than how.

Check the behaviour not pieces of code.

Robust testing.

More readable. More readable tests.

Page 9: Testing view controllers with Quick and Nimble

Quick and Nimble

• Quick is a Behaviour-driven development framework for Swift and Objective-C.

• Inspired by RSpec,Specta, and Ginkgo.

• Nimble is a Matcher Framework also for both languages.

• Provide more clear expectations.

Page 10: Testing view controllers with Quick and Nimble

Example

Page 11: Testing view controllers with Quick and Nimble

iOS App: Pony

• PonyTabController: UITabBarController.

• Responsible for presenting the app intro

Page 12: Testing view controllers with Quick and Nimble

PonyTabController

public class PonyTabController: UITabBarController { override public func viewDidAppear(animated: Bool) { //… if !appIntroHasBeenPresented {

presentViewController(appIntroViewController,…) { appIntroViewController.dismissButtonTapHandler = {

appIntroHasBeenPresented = true self.dismissViewControllerAnimated(true,…) }

• Check if app intro has been presented ⚠ • Present app intro. ⚠ • Dismiss if handler is called. ⚠

Page 13: Testing view controllers with Quick and Nimble

Testing: Present app intro ⚠import Quick import Nimble

class PonyTabBarControllerSpec: QuickSpec { override func spec() { describe(“.viewDidAppear"){ context("when app intro had never been dismissed"){ it("should be presented”){

expect(tabBarController.presentedViewController) .toEventually(beAnInstanceOf(AppIntroViewController))

} } } //…

• Pre-conditions are still missing. ⚠

• The object under test is not being invoked. ⚠

Page 14: Testing view controllers with Quick and Nimble

Testing: Present app intro ⚠import Pony //… var tabBarController: PonyTabController! describe(".viewDidAppear"){

context("when app intro had never been dismissed"){

beforeEach { // 1 Arrange: tabBarController = storyboard.instantiateInitialViewController() as! PonyTabController

// 2 Act: let _ = tabBarController.view

} it("should be presented”){

// 3 Assert: expect(tabBarController.presentedViewController).toEventually(beAnInstanceOf(AppIntro…))

} }

} } 1. Pre-conditions. ✅

2. The object under test is being invoked. ✅

3. Asserting. ⚠

Page 15: Testing view controllers with Quick and Nimble

"Arrange-Act-Assert"

• Pattern for arranging and formatting code in Tests methods.

• Benefit:

• Clearly separates what is being tested from the setup and verification steps.

Page 16: Testing view controllers with Quick and Nimble

Testing: Present app intro ⚠import Pony //… var tabBarController: PonyTabController! describe(".viewDidAppear"){

context("when app intro had never been dismissed"){

beforeEach { // 1 Arrange: tabBarController = storyboard.instantiateInitialViewController() as! PonyTabController

// 2 Act: let _ = tabBarController.view

} it("should be presented”){

// 3 Assert: expect(tabBarController.presentedViewController).toEventually(beAnInstanceOf(AppIntro…))

} }

} } 1. Pre-conditions. ✅

2. The object under test is being invoked. ✅

3. Asserting. ⚠

Page 17: Testing view controllers with Quick and Nimble

Testing: Present app intro ✅

PonyTabController: UITabBarController { override public func viewDidAppear(animated: Bool) { //… presentViewController(appIntroViewController,…) {

} //…

Warning: Attempt to present <AppIntroViewController: 0x1e56e0a0> on <PonyTabController: 0x1ec3e000>

whose view is not in the window hierarchy!

Page 18: Testing view controllers with Quick and Nimble

Testing: Present app intro ✅import Pony //… var tabBarController: PonyTabController! describe(".viewDidAppear"){

context("when app intro had never been dismissed"){

beforeEach { // 1 Arrange: tabBarController = storyboard.instantiateInitialViewController() as! PonyTabController

// 2 Act: UIApplication.sharedApplication().keyWindow?.rootViewController = tabBarController

it("should be presented”){

// 3 Assert: expect(tabBarController.presentedViewController).toEventually(beAnInstanceOf(AppIntro…))

} }

} } 1. Pre-conditions. ✅

2. The object under test is being invoked. ✅

3. Asserting. ✅

Page 19: Testing view controllers with Quick and Nimble

Testing: Dismiss app intro ⚠//… context("when app intro had never been dismissed”){ //… context("and dismiss button was tapped") { beforeEach { // Arrange: appIntroHasBeenPresented = false

// Act: tabBarController.beginAppearanceTransition(true, animated: false) tabBarController.endAppearanceTransition()

var appIntroViewController = tabBarController.presentedViewController as! AppIntroViewController appIntroViewController.dismissButton! .sendActionsForControlEvents(UIControlEvents.TouchUpInside)

} it("should dismiss app intro"){ // Assert: expect(tabBarController.presentedViewController).toEventually(beNil())

} //…

}• Another context.

• beginAppearanceTransition: will trigger viewWillAppear.

• endAppearanceTransition: will trigger viewDidAppear.

• sendActionsForControlEvents: simulate tap on the dismiss button

Page 20: Testing view controllers with Quick and Nimble

Testing: Dismiss app intro ✅//… context("when app intro had never been dismissed”){

//… context("and dismiss button was tapped") {

beforeEach { // Arrange: appIntroHasBeenPresented = false

// Act: tabBarController.beginAppearanceTransition(true, animated: false) tabBarController.endAppearanceTransition()

var appIntroViewController = tabBarController.presentedViewController as! AppIntroViewController appIntroViewController.dismissButton!

.sendActionsForControlEvents(UIControlEvents.TouchUpInside) }

it("should set appIntroHasBeenPresented to true""){

// Assert: expect(appIntroHasBeenPresented).to(beTrue())

}

it("should dismiss app intro"){ // Assert: expect(tabBarController.presentedViewController).toEventually(beNil())

} //…

}

• Set app intro presented to be true. ✅

• Will dismiss if the button tap handler is called. ✅

Page 21: Testing view controllers with Quick and Nimble

Extra//…

waitUntil { done in tabBarController.dismissViewControllerAnimated(false) { done()

} }

}

//…

waitUntil { done in NSThread.sleepForTimeInterval(0.5) done()

} //…

• waitUntil is a function provided by Nimble where you can execute something inside it closure and call done() when is ready.

• Useful when waiting for a callback.

Page 22: Testing view controllers with Quick and Nimble

Tested ✅

• Verifying the app intro will be presented if it had never been dismissed. ✅

• Set app intro presented to be true. ✅

• Will dismiss if the button tap handler is called. ✅

//… it("should be presented”){ expect(tabBarController.presentedViewController).toEventually(beAnInstanceOf(AppIntro…))

} //… it("should dismiss app intro"){ expect(tabBarController.presentedViewController).toEventually(beNil())

}

it("should set appIntroHasBeenPresented to true"){ expect(appIntroHasBeenPresented).to(beTrue()) } //…

Page 23: Testing view controllers with Quick and Nimble

Conclusion

Page 24: Testing view controllers with Quick and Nimble

Conclusion• BDD + Quick and Nimble can help you get more

meaningful tests for view controllers.

• There is a lifecycle that must be followed, i.e: you can’t present a V.C. if there another already being presented or the view is not part of the hierarchy.

• UIKit provide public methods that can help.

• Don’t create massive view controllers.

Page 25: Testing view controllers with Quick and Nimble

Questions ?

Page 26: Testing view controllers with Quick and Nimble

References

• https://github.com/Quick/Quick • http://realm.io/news/testing-in-swift/ • http://www.slideshare.net/bgesiak/everything-you-never-

wanted • http://c2.com/cgi/wiki?ArrangeActAssert