8. 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.
9. 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.
11. iOS App: Pony
• PonyTabController:
UITabBarController.
• Responsible for
presenting the app
intro
12. 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. ⚠
13. 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. ⚠
14. 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. ⚠
15. "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.
16. 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. ⚠
17. 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!
18. 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. ✅
19. 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
20. 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. ✅
21. 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.
22. 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())
}
//…
24. 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.