SlideShare a Scribd company logo
Writing Swift code
with great testability
⌘ U
+how became my new ⌘ R
+
@johnsundell
or,
⌘ U
+how became my new ⌘ R
+
Unit tests are a waste of time. It’s faster and easier to do
manual testing, and focus my coding time on writing
actual code.
Unbox, Wrap, Hub Framework, etc…
Integrating, re-building, manual testing⏳
Bugs, regressions, unwanted behavior"
⌘ U
+how became my new ⌘ R
+
Automating testing = Focus on coding#
$
Spotify app
Hub Framework 20x faster compile times!
Tests provide documentation of intent%
Tests let me move faster, I don’t have to constantly run
the app, but can instead verify most of its working parts
in isolation, and make quick iterations.
3 tips on how to work
with unit testing in
1 Design your code for testability
What makes code easy to test?
Unified
Input/Output
Injected
dependencies
State is kept
local
1 Design your code for testability
class FileLoader {
static let shared = FileLoader()
private let cache = Cache()
func file(named fileName: String) throws -> File {
if let cachedFile = cache.file(named: fileName) {
return cachedFile
}
let bundle = Bundle.main
guard let url = bundle.url(forResource: fileName, withExtension: nil) else {
throw NSError(domain: "com.johnsundell.myapp.fileLoader", code: 7, userInfo: nil)
}
let data = try Data(contentsOf: url)
let file = File(data: data)
cache.cache(file: file, name: fileName)
return file
}
}
Unified
Input/Output
Injected
dependencies
State is kept
local
class FileLoader {
static let shared = FileLoader()
private let cache = Cache()
func file(named fileName: String) throws -> File {
if let cachedFile = cache.file(named: fileName) {
return cachedFile
}
let bundle = Bundle.main
guard let url = bundle.url(forResource: fileName, withExtension: nil) else {
throw NSError(domain: "com.johnsundell.myapp.fileLoader", code: 7, userInfo: nil)
}
let data = try Data(contentsOf: url)
let file = File(data: data)
cache.cache(file: file, name: fileName)
return file
}
}
Unified
Input/Output
Injected
dependencies
State is kept
local
Unified
Input/Output
class FileLoader {
static let shared = FileLoader()
private let cache = Cache()
func file(named fileName: String) throws -> File {
if let cachedFile = cache.file(named: fileName) {
return cachedFile
}
let bundle = Bundle.main
guard let url = bundle.url(forResource: fileName, withExtension: nil) else {
throw NSError(domain: "com.johnsundell.myapp.fileLoader", code: 7, userInfo: nil)
}
let data = try Data(contentsOf: url)
let file = File(data: data)
cache.cache(file: file, name: fileName)
return file
}
}
Unified
Input/Output
Injected
dependencies
State is kept
local
Unified
Input/Output
State is kept
local
class FileLoader {
static let shared = FileLoader()
private let cache = Cache()
func file(named fileName: String) throws -> File {
if let cachedFile = cache.file(named: fileName) {
return cachedFile
}
let bundle = Bundle.main
guard let url = bundle.url(forResource: fileName, withExtension: nil) else {
throw NSError(domain: "com.johnsundell.myapp.fileLoader", code: 7, userInfo: nil)
}
let data = try Data(contentsOf: url)
let file = File(data: data)
cache.cache(file: file, name: fileName)
return file
}
}
Unified
Input/Output
Injected
dependencies
State is kept
local
Unified
Input/Output
State is kept
local
Injected
dependencies
class FileLoader {
static let shared = FileLoader()
private let cache = Cache()
func file(named fileName: String) throws -> File {
if let cachedFile = cache.file(named: fileName) {
return cachedFile
}
let bundle = Bundle.main
guard let url = bundle.url(forResource: fileName, withExtension: nil) else {
throw NSError(domain: "com.johnsundell.myapp.fileLoader", code: 7, userInfo: nil)
}
let data = try Data(contentsOf: url)
let file = File(data: data)
cache.cache(file: file, name: fileName)
return file
}
}
Unified
Input/Output
Injected
dependencies
State is kept
local
Unified
Input/Output
State is kept
local
Injected
dependencies
enum FileLoaderError: Error {
case invalidFileName(String)
case invalidFileURL(URL)
}
Dedicated error type
throw FileLoaderError.invalidFileName(fileName)
do {
let data = try Data(contentsOf: url)
let file = File(data: data)
cache.cache(file: file, name: fileName)
return file
} catch {
throw FileLoaderError.invalidFileURL(url)
}
}
}
Unified error output
Unified
Input/Output
class FileLoader {
static let shared = FileLoader()
private let cache = Cache()
func file(named fileName: String) throws -> File {
if let cachedFile = cache.file(named: fileName) {
return cachedFile
}
let bundle = Bundle.main
guard let url = bundle.url(forResource: fileName, withExtension: nil) else {
throw NSError(domain: "com.johnsundell.myapp.fileLoader", code: 7, userInfo: nil)
}
let data = try Data(contentsOf: url)
let file = File(data: data)
cache.cache(file: file, name: fileName)
return file
}
}
Unified
Input/Output
Injected
dependencies
State is kept
local
Unified
Input/Output
State is kept
local
Injected
dependencies
enum FileLoaderError: Error {
case invalidFileName(String)
case invalidFileURL(URL)
}
throw FileLoaderError.invalidFileName(fileName)
do {
let data = try Data(contentsOf: url)
let file = File(data: data)
cache.cache(file: file, name: fileName)
return file
} catch {
throw FileLoaderError.invalidFileURL(url)
}
}
}
Unified
Input/Output
State is kept
local
class FileLoader {
private let cache = Cache()
Unified
Input/Output
Injected
dependencies
State is kept
local
Unified
Input/Output
State is kept
local
Injected
dependencies
Unified
Input/Output
State is kept
local
private let bundle: Bundle
init(cache: Cache
self.cache = cache
self.bundle = bundle
}
, bundle: Bundle) {
func file(named fileName: String) throws -> File {
if let cachedFile = cache.file(named: fileName) {
return cachedFile
}
let bundle = Bundle.main
guard let url = bundle.url(forResource: fileName, withExtension: nil) else {
throw FileLoaderError.invalidFileName(fileName)
}
do {
let data = try Data(contentsOf: url)
let file = File(data: data)
cache.cache(file: file, name: fileName)
return file
} catch {
throw FileLoaderError.invalidFileURL(url)
}
}
}
Dependency injection (with defaults)
= .init(), bundle: Bundle = .main) {
bundle
Using injected dependencies
Injected
dependencies
Let’s write a test! &
1 Design your code for testability
2 Use access control to create
clear API boundaries
2 Use access control to create
clear API boundaries
APIInput Asserts
Unit test
Code
Integration test
2 Use access control to create
clear API boundaries
private fileprivate internal public open
2 Use access control to create
clear API boundaries
public class SendMessageViewController: UIViewController {
public var recipients: [User]
public var title: String
public var message: String
public var recipientsPicker: UserPickerView?
public var titleTextField: UITextField?
public var messageTextField: UITextField?
}
2 Use access control to create
clear API boundaries
public class SendMessageViewController: UIViewController {
private var recipients: [User]
private var title: String
private var message: String
private var recipientsPicker: UserPickerView?
private var titleTextField: UITextField?
private var messageTextField: UITextField?
}
2 Use access control to create
clear API boundaries
}
func update(recepients: [User]? = nil, title: String? = nil, message: String? = nil) {
if let recepients = recepients {
self.recipients = recepients
}
// Same for title & message
}
public class SendMessageViewController: UIViewController {
private var recipients: [User]
private var title: String
private var message: String
private var recipientsPicker: UserPickerView?
private var titleTextField: UITextField?
private var messageTextField: UITextField? Single API entry point
2 Use access control to create
clear API boundaries
Frameworks
2 Use access control to create
clear API boundaries
Input Asserts
Unit test
Integration test
2 Use access control to create
clear API boundaries
App
Models
Views
Logic
2 Use access control to create
clear API boundaries
App
Models
Views
LogicKit
Kit
($ brew install swiftplate)
3 Avoid mocks to avoid getting tied down
into implementation details
3 Avoid mocks to avoid getting tied down
into implementation details
' Mocks are “fake” objects that are used in tests to be
able to assert that certain things happen as expected.
// Objective-C (using OCMockito)
NSBundle *bundle = mock([NSBundle class]);
[given([bundle pathForResource:anything() ofType:anything()]) willReturn:@“path”];
FileLoader *fileLoader = [[FileLoader alloc] initWithBundle:bundle];
XCTAssertNotNil([fileLoader fileNamed:@"file"]);
// Swift (partial mocking)
class MockBundle: Bundle {
var mockPath: String?
override func path(forResource name: String?, ofType ext: String?) -> String? {
return mockPath
}
}
let bundle = MockBundle()
bundle.mockPath = "path"
let fileLoader = FileLoader(bundle: bundle)
XCTAssertNotNil(fileLoader.file(named: "file"))
// Swift (no mocking)
let bundle = Bundle(for: type(of: self))
let fileLoader = FileLoader(bundle: bundle)
XCTAssertNotNil(fileLoader.file(named: "file"))
3 Avoid mocks to avoid getting tied down
into implementation details
class ImageLoader {
func loadImage(named imageName: String) -> UIImage? {
return UIImage(named: imageName)
}
}
class ImageViewController: UIViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
imageView.image = imageLoader.loadImage(named: imageName)
}
}
// Test using mocking
class ImageViewControllerTests: XCTestCase {
func testImageLoadedOnViewWillAppear() {
class MockImageLoader: ImageLoader {
private(set) var loadedImageNames = [String]()
override func loadImage(named name: String) -> UIImage {
loadedImageNames.append(name)
return UIImage()
}
}
let imageLoader = MockImageLoader()
let vc = ImageViewController(imageLoader: imageLoader)
vc.imageName = "image"
vc.viewWillAppear(false)
XCTAssertEqual(imageLoader.loadedImageNames, ["image"])
}
}
Manually implemented, partial mock
Asserting that an image was loaded by verifying
what our mock captured
Capture loaded image names
3 Avoid mocks to avoid getting tied down
into implementation details
class ImageLoader {
private let preloadedImages: [String : UIImage]
init(preloadedImages: [String : UIImage] = [:]) {
self.preloadedImages = preloadedImages
}
func loadImage(named imageName: String) -> UIImage? {
if let preloadedImage = preloadedImages[imageName] {
return preloadedImage
}
return UIImage(named: imageName)
}
}
// Test without mocking
class ImageViewControllerTests: XCTestCase {
func testImageLoadedOnViewWillAppear() {
let image = UIImage()
let imageLoader = ImageLoader(images: ["image" : image])
let vc = ImageViewController(imageLoader: imageLoader)
vc.imageName = "image"
vc.viewWillAppear(false)
XCTAssertEqual(vc.image, image)
}
}
Optionally enable preloaded images
to be injected
Use preloaded image if any exists
Compare against actually rendered image,
instead of relying on mock capturing
3 Avoid mocks to avoid getting tied down
into implementation details
However, sometimes you do
need mocks, so let’s make it
easy to use them! (
3 Avoid mocks to avoid getting tied down
into implementation details
To summarize
1 Design your code for testability
2 Use access control to create
clear API boundaries
3 Avoid mocks to avoid getting tied down
into implementation details
No tests? )
No problem! *
Just start somewhere (
Set goals for test coverage &
@johnsundell
/johnsundell

More Related Content

What's hot

groovy & grails - lecture 2
groovy & grails - lecture 2groovy & grails - lecture 2
groovy & grails - lecture 2
Alexandre Masselot
 
Dpilot Source Code With ScreenShots
Dpilot Source Code With ScreenShots Dpilot Source Code With ScreenShots
Dpilot Source Code With ScreenShots
DeepAnshu Sharma
 
Source Code for Dpilot
Source Code for Dpilot Source Code for Dpilot
Source Code for Dpilot
Nidhi Chauhan
 
Testing Web Applications with GEB
Testing Web Applications with GEBTesting Web Applications with GEB
Testing Web Applications with GEB
Howard Lewis Ship
 
Webinar: MongoDB Persistence with Java and Morphia
Webinar: MongoDB Persistence with Java and MorphiaWebinar: MongoDB Persistence with Java and Morphia
Webinar: MongoDB Persistence with Java and Morphia
MongoDB
 
Reactive Web - Servlet & Async, Non-blocking I/O
Reactive Web - Servlet & Async, Non-blocking I/OReactive Web - Servlet & Async, Non-blocking I/O
Reactive Web - Servlet & Async, Non-blocking I/O
Arawn Park
 
Biopython
BiopythonBiopython
Biopython
Karin Lagesen
 
Backbone.js: Run your Application Inside The Browser
Backbone.js: Run your Application Inside The BrowserBackbone.js: Run your Application Inside The Browser
Backbone.js: Run your Application Inside The Browser
Howard Lewis Ship
 
Using Fuzzy Code Search to Link Code Fragments in Discussions to Source Code
Using Fuzzy Code Search to Link Code Fragments in Discussions to Source CodeUsing Fuzzy Code Search to Link Code Fragments in Discussions to Source Code
Using Fuzzy Code Search to Link Code Fragments in Discussions to Source Code
Nicolas Bettenburg
 
Iss letcure 7_8
Iss letcure 7_8Iss letcure 7_8
Iss letcure 7_8Ali Habeeb
 
What's new in Liferay Mobile SDK 2.0 for Android
What's new in Liferay Mobile SDK 2.0 for AndroidWhat's new in Liferay Mobile SDK 2.0 for Android
What's new in Liferay Mobile SDK 2.0 for Android
Silvio Gustavo de Oliveira Santos
 
Tomcat连接池配置方法V2.1
Tomcat连接池配置方法V2.1Tomcat连接池配置方法V2.1
Tomcat连接池配置方法V2.1
Zianed Hou
 
Grails/Groovyによる開発事例紹介
Grails/Groovyによる開発事例紹介Grails/Groovyによる開発事例紹介
Grails/Groovyによる開発事例紹介Kiyotaka Oku
 
Spock: A Highly Logical Way To Test
Spock: A Highly Logical Way To TestSpock: A Highly Logical Way To Test
Spock: A Highly Logical Way To Test
Howard Lewis Ship
 
Finding Clojure
Finding ClojureFinding Clojure
Finding Clojure
Matthew McCullough
 
Lab4
Lab4Lab4
13 networking, mobile services, and authentication
13   networking, mobile services, and authentication13   networking, mobile services, and authentication
13 networking, mobile services, and authentication
WindowsPhoneRocks
 
NIO.2, the I/O API for the future
NIO.2, the I/O API for the futureNIO.2, the I/O API for the future
NIO.2, the I/O API for the future
Masoud Kalali
 
神に近づくx/net/context (Finding God with x/net/context)
神に近づくx/net/context (Finding God with x/net/context)神に近づくx/net/context (Finding God with x/net/context)
神に近づくx/net/context (Finding God with x/net/context)
guregu
 

What's hot (19)

groovy & grails - lecture 2
groovy & grails - lecture 2groovy & grails - lecture 2
groovy & grails - lecture 2
 
Dpilot Source Code With ScreenShots
Dpilot Source Code With ScreenShots Dpilot Source Code With ScreenShots
Dpilot Source Code With ScreenShots
 
Source Code for Dpilot
Source Code for Dpilot Source Code for Dpilot
Source Code for Dpilot
 
Testing Web Applications with GEB
Testing Web Applications with GEBTesting Web Applications with GEB
Testing Web Applications with GEB
 
Webinar: MongoDB Persistence with Java and Morphia
Webinar: MongoDB Persistence with Java and MorphiaWebinar: MongoDB Persistence with Java and Morphia
Webinar: MongoDB Persistence with Java and Morphia
 
Reactive Web - Servlet & Async, Non-blocking I/O
Reactive Web - Servlet & Async, Non-blocking I/OReactive Web - Servlet & Async, Non-blocking I/O
Reactive Web - Servlet & Async, Non-blocking I/O
 
Biopython
BiopythonBiopython
Biopython
 
Backbone.js: Run your Application Inside The Browser
Backbone.js: Run your Application Inside The BrowserBackbone.js: Run your Application Inside The Browser
Backbone.js: Run your Application Inside The Browser
 
Using Fuzzy Code Search to Link Code Fragments in Discussions to Source Code
Using Fuzzy Code Search to Link Code Fragments in Discussions to Source CodeUsing Fuzzy Code Search to Link Code Fragments in Discussions to Source Code
Using Fuzzy Code Search to Link Code Fragments in Discussions to Source Code
 
Iss letcure 7_8
Iss letcure 7_8Iss letcure 7_8
Iss letcure 7_8
 
What's new in Liferay Mobile SDK 2.0 for Android
What's new in Liferay Mobile SDK 2.0 for AndroidWhat's new in Liferay Mobile SDK 2.0 for Android
What's new in Liferay Mobile SDK 2.0 for Android
 
Tomcat连接池配置方法V2.1
Tomcat连接池配置方法V2.1Tomcat连接池配置方法V2.1
Tomcat连接池配置方法V2.1
 
Grails/Groovyによる開発事例紹介
Grails/Groovyによる開発事例紹介Grails/Groovyによる開発事例紹介
Grails/Groovyによる開発事例紹介
 
Spock: A Highly Logical Way To Test
Spock: A Highly Logical Way To TestSpock: A Highly Logical Way To Test
Spock: A Highly Logical Way To Test
 
Finding Clojure
Finding ClojureFinding Clojure
Finding Clojure
 
Lab4
Lab4Lab4
Lab4
 
13 networking, mobile services, and authentication
13   networking, mobile services, and authentication13   networking, mobile services, and authentication
13 networking, mobile services, and authentication
 
NIO.2, the I/O API for the future
NIO.2, the I/O API for the futureNIO.2, the I/O API for the future
NIO.2, the I/O API for the future
 
神に近づくx/net/context (Finding God with x/net/context)
神に近づくx/net/context (Finding God with x/net/context)神に近づくx/net/context (Finding God with x/net/context)
神に近づくx/net/context (Finding God with x/net/context)
 

Viewers also liked

Component-driven UIs - Mobile Era 2016
Component-driven UIs - Mobile Era 2016Component-driven UIs - Mobile Era 2016
Component-driven UIs - Mobile Era 2016
John Sundell
 
Backend-driven native UIs
Backend-driven native UIsBackend-driven native UIs
Backend-driven native UIs
John Sundell
 
Making Swift even safer
Making Swift even saferMaking Swift even safer
Making Swift even safer
Denis Fileev
 
Unit testing in iOS featuring OCUnit, GHUnit & OCMock
Unit testing in iOS featuring OCUnit, GHUnit & OCMockUnit testing in iOS featuring OCUnit, GHUnit & OCMock
Unit testing in iOS featuring OCUnit, GHUnit & OCMockRobot Media
 
Dynamic, native, backend-driven UIs - App Builders 2016
Dynamic, native, backend-driven UIs - App Builders 2016Dynamic, native, backend-driven UIs - App Builders 2016
Dynamic, native, backend-driven UIs - App Builders 2016
John Sundell
 
Building component-driven UIs at Spotify
Building component-driven UIs at SpotifyBuilding component-driven UIs at Spotify
Building component-driven UIs at Spotify
John Sundell
 
How To Build iOS Apps Without interface Builder
How To Build iOS Apps Without interface BuilderHow To Build iOS Apps Without interface Builder
How To Build iOS Apps Without interface Builder
dasdom
 
I017345357
I017345357I017345357
I017345357
IOSR Journals
 
196309903 q-answer
196309903 q-answer196309903 q-answer
196309903 q-answer
homeworkping3
 
Evaluation question 7
Evaluation question 7Evaluation question 7
Evaluation question 7
callum cook
 
education people in our society
education people in our society education people in our society
education people in our society
Alka Srivastava
 
Photos lar 2
Photos lar 2Photos lar 2
Photos lar 2
crcusteam
 
Mocking In Swift
Mocking In SwiftMocking In Swift
Mocking In Swift
dasdom
 
Tdd
TddTdd
Tdd
dasdom
 
Презентация ученика 6 класса "Дед Мороз и его двойники".
Презентация ученика 6 класса "Дед Мороз и его двойники".Презентация ученика 6 класса "Дед Мороз и его двойники".
Презентация ученика 6 класса "Дед Мороз и его двойники".
Svetlana Krasnoselskaya
 
Trancision 2017
Trancision 2017Trancision 2017
Trancision 2017
reino holanda
 
Representation of nationality in captain america
Representation of nationality  in captain americaRepresentation of nationality  in captain america
Representation of nationality in captain americaddoggart
 
Denis Lebedev, Swift
Denis  Lebedev, SwiftDenis  Lebedev, Swift
Denis Lebedev, SwiftYandex
 
Make Up Tutorials
Make Up TutorialsMake Up Tutorials
Make Up Tutorials
BaileyMcMeekin
 
UI Testing with Earl Grey
UI Testing with Earl GreyUI Testing with Earl Grey
UI Testing with Earl Grey
Shyam Bhat
 

Viewers also liked (20)

Component-driven UIs - Mobile Era 2016
Component-driven UIs - Mobile Era 2016Component-driven UIs - Mobile Era 2016
Component-driven UIs - Mobile Era 2016
 
Backend-driven native UIs
Backend-driven native UIsBackend-driven native UIs
Backend-driven native UIs
 
Making Swift even safer
Making Swift even saferMaking Swift even safer
Making Swift even safer
 
Unit testing in iOS featuring OCUnit, GHUnit & OCMock
Unit testing in iOS featuring OCUnit, GHUnit & OCMockUnit testing in iOS featuring OCUnit, GHUnit & OCMock
Unit testing in iOS featuring OCUnit, GHUnit & OCMock
 
Dynamic, native, backend-driven UIs - App Builders 2016
Dynamic, native, backend-driven UIs - App Builders 2016Dynamic, native, backend-driven UIs - App Builders 2016
Dynamic, native, backend-driven UIs - App Builders 2016
 
Building component-driven UIs at Spotify
Building component-driven UIs at SpotifyBuilding component-driven UIs at Spotify
Building component-driven UIs at Spotify
 
How To Build iOS Apps Without interface Builder
How To Build iOS Apps Without interface BuilderHow To Build iOS Apps Without interface Builder
How To Build iOS Apps Without interface Builder
 
I017345357
I017345357I017345357
I017345357
 
196309903 q-answer
196309903 q-answer196309903 q-answer
196309903 q-answer
 
Evaluation question 7
Evaluation question 7Evaluation question 7
Evaluation question 7
 
education people in our society
education people in our society education people in our society
education people in our society
 
Photos lar 2
Photos lar 2Photos lar 2
Photos lar 2
 
Mocking In Swift
Mocking In SwiftMocking In Swift
Mocking In Swift
 
Tdd
TddTdd
Tdd
 
Презентация ученика 6 класса "Дед Мороз и его двойники".
Презентация ученика 6 класса "Дед Мороз и его двойники".Презентация ученика 6 класса "Дед Мороз и его двойники".
Презентация ученика 6 класса "Дед Мороз и его двойники".
 
Trancision 2017
Trancision 2017Trancision 2017
Trancision 2017
 
Representation of nationality in captain america
Representation of nationality  in captain americaRepresentation of nationality  in captain america
Representation of nationality in captain america
 
Denis Lebedev, Swift
Denis  Lebedev, SwiftDenis  Lebedev, Swift
Denis Lebedev, Swift
 
Make Up Tutorials
Make Up TutorialsMake Up Tutorials
Make Up Tutorials
 
UI Testing with Earl Grey
UI Testing with Earl GreyUI Testing with Earl Grey
UI Testing with Earl Grey
 

Similar to Writing Swift code with great testability

Unit testing
Unit testingUnit testing
Unit testing
davidahaskins
 
Refactoring In Tdd The Missing Part
Refactoring In Tdd The Missing PartRefactoring In Tdd The Missing Part
Refactoring In Tdd The Missing PartGabriele Lana
 
Gradleintroduction 111010130329-phpapp01
Gradleintroduction 111010130329-phpapp01Gradleintroduction 111010130329-phpapp01
Gradleintroduction 111010130329-phpapp01Tino Isnich
 
Input output files in java
Input output files in javaInput output files in java
Input output files in java
Kavitha713564
 
Introduction to PowerShell
Introduction to PowerShellIntroduction to PowerShell
Introduction to PowerShell
Boulos Dib
 
Developing IT infrastructures with Puppet
Developing IT infrastructures with PuppetDeveloping IT infrastructures with Puppet
Developing IT infrastructures with Puppet
Alessandro Franceschi
 
Tornadoweb
TornadowebTornadoweb
Tornadoweb
Osman Yuksel
 
Lambda Chops - Recipes for Simpler, More Expressive Code
Lambda Chops - Recipes for Simpler, More Expressive CodeLambda Chops - Recipes for Simpler, More Expressive Code
Lambda Chops - Recipes for Simpler, More Expressive Code
Ian Robertson
 
File Handling.pptx
File Handling.pptxFile Handling.pptx
File Handling.pptx
PragatiSutar4
 
Iwatch tech 1
Iwatch tech 1Iwatch tech 1
Iwatch tech 1
ShailajaMca
 
Grâce aux tags Varnish, j'ai switché ma prod sur Raspberry Pi
Grâce aux tags Varnish, j'ai switché ma prod sur Raspberry PiGrâce aux tags Varnish, j'ai switché ma prod sur Raspberry Pi
Grâce aux tags Varnish, j'ai switché ma prod sur Raspberry Pi
Jérémy Derussé
 
Zend framework 03 - singleton factory data mapper caching logging
Zend framework 03 - singleton factory data mapper caching loggingZend framework 03 - singleton factory data mapper caching logging
Zend framework 03 - singleton factory data mapper caching logging
Tricode (part of Dept)
 
Java I/o streams
Java I/o streamsJava I/o streams
Java I/o streams
Hamid Ghorbani
 
Build powerfull and smart web applications with Symfony2
Build powerfull and smart web applications with Symfony2Build powerfull and smart web applications with Symfony2
Build powerfull and smart web applications with Symfony2
Hugo Hamon
 
DIWE - File handling with PHP
DIWE - File handling with PHPDIWE - File handling with PHP
DIWE - File handling with PHP
Rasan Samarasinghe
 
File & Exception Handling in C++.pptx
File & Exception Handling in C++.pptxFile & Exception Handling in C++.pptx
File & Exception Handling in C++.pptx
RutujaTandalwade
 
10 Cool Facts about Gradle
10 Cool Facts about Gradle10 Cool Facts about Gradle
10 Cool Facts about GradleEvgeny Goldin
 
Hack ASP.NET website
Hack ASP.NET websiteHack ASP.NET website
Hack ASP.NET website
Positive Hack Days
 
hibernate with JPA
hibernate with JPAhibernate with JPA
hibernate with JPA
Mohammad Faizan
 

Similar to Writing Swift code with great testability (20)

Unit testing
Unit testingUnit testing
Unit testing
 
Refactoring In Tdd The Missing Part
Refactoring In Tdd The Missing PartRefactoring In Tdd The Missing Part
Refactoring In Tdd The Missing Part
 
Gradle Introduction
Gradle IntroductionGradle Introduction
Gradle Introduction
 
Gradleintroduction 111010130329-phpapp01
Gradleintroduction 111010130329-phpapp01Gradleintroduction 111010130329-phpapp01
Gradleintroduction 111010130329-phpapp01
 
Input output files in java
Input output files in javaInput output files in java
Input output files in java
 
Introduction to PowerShell
Introduction to PowerShellIntroduction to PowerShell
Introduction to PowerShell
 
Developing IT infrastructures with Puppet
Developing IT infrastructures with PuppetDeveloping IT infrastructures with Puppet
Developing IT infrastructures with Puppet
 
Tornadoweb
TornadowebTornadoweb
Tornadoweb
 
Lambda Chops - Recipes for Simpler, More Expressive Code
Lambda Chops - Recipes for Simpler, More Expressive CodeLambda Chops - Recipes for Simpler, More Expressive Code
Lambda Chops - Recipes for Simpler, More Expressive Code
 
File Handling.pptx
File Handling.pptxFile Handling.pptx
File Handling.pptx
 
Iwatch tech 1
Iwatch tech 1Iwatch tech 1
Iwatch tech 1
 
Grâce aux tags Varnish, j'ai switché ma prod sur Raspberry Pi
Grâce aux tags Varnish, j'ai switché ma prod sur Raspberry PiGrâce aux tags Varnish, j'ai switché ma prod sur Raspberry Pi
Grâce aux tags Varnish, j'ai switché ma prod sur Raspberry Pi
 
Zend framework 03 - singleton factory data mapper caching logging
Zend framework 03 - singleton factory data mapper caching loggingZend framework 03 - singleton factory data mapper caching logging
Zend framework 03 - singleton factory data mapper caching logging
 
Java I/o streams
Java I/o streamsJava I/o streams
Java I/o streams
 
Build powerfull and smart web applications with Symfony2
Build powerfull and smart web applications with Symfony2Build powerfull and smart web applications with Symfony2
Build powerfull and smart web applications with Symfony2
 
DIWE - File handling with PHP
DIWE - File handling with PHPDIWE - File handling with PHP
DIWE - File handling with PHP
 
File & Exception Handling in C++.pptx
File & Exception Handling in C++.pptxFile & Exception Handling in C++.pptx
File & Exception Handling in C++.pptx
 
10 Cool Facts about Gradle
10 Cool Facts about Gradle10 Cool Facts about Gradle
10 Cool Facts about Gradle
 
Hack ASP.NET website
Hack ASP.NET websiteHack ASP.NET website
Hack ASP.NET website
 
hibernate with JPA
hibernate with JPAhibernate with JPA
hibernate with JPA
 

Recently uploaded

AI for Every Business: Unlocking Your Product's Universal Potential by VP of ...
AI for Every Business: Unlocking Your Product's Universal Potential by VP of ...AI for Every Business: Unlocking Your Product's Universal Potential by VP of ...
AI for Every Business: Unlocking Your Product's Universal Potential by VP of ...
Product School
 
The Future of Platform Engineering
The Future of Platform EngineeringThe Future of Platform Engineering
The Future of Platform Engineering
Jemma Hussein Allen
 
Bits & Pixels using AI for Good.........
Bits & Pixels using AI for Good.........Bits & Pixels using AI for Good.........
Bits & Pixels using AI for Good.........
Alison B. Lowndes
 
Epistemic Interaction - tuning interfaces to provide information for AI support
Epistemic Interaction - tuning interfaces to provide information for AI supportEpistemic Interaction - tuning interfaces to provide information for AI support
Epistemic Interaction - tuning interfaces to provide information for AI support
Alan Dix
 
FIDO Alliance Osaka Seminar: FIDO Security Aspects.pdf
FIDO Alliance Osaka Seminar: FIDO Security Aspects.pdfFIDO Alliance Osaka Seminar: FIDO Security Aspects.pdf
FIDO Alliance Osaka Seminar: FIDO Security Aspects.pdf
FIDO Alliance
 
GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using Deplo...
GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using Deplo...GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using Deplo...
GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using Deplo...
James Anderson
 
Connector Corner: Automate dynamic content and events by pushing a button
Connector Corner: Automate dynamic content and events by pushing a buttonConnector Corner: Automate dynamic content and events by pushing a button
Connector Corner: Automate dynamic content and events by pushing a button
DianaGray10
 
FIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdf
FIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdfFIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdf
FIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdf
FIDO Alliance
 
Securing your Kubernetes cluster_ a step-by-step guide to success !
Securing your Kubernetes cluster_ a step-by-step guide to success !Securing your Kubernetes cluster_ a step-by-step guide to success !
Securing your Kubernetes cluster_ a step-by-step guide to success !
KatiaHIMEUR1
 
UiPath Test Automation using UiPath Test Suite series, part 3
UiPath Test Automation using UiPath Test Suite series, part 3UiPath Test Automation using UiPath Test Suite series, part 3
UiPath Test Automation using UiPath Test Suite series, part 3
DianaGray10
 
Accelerate your Kubernetes clusters with Varnish Caching
Accelerate your Kubernetes clusters with Varnish CachingAccelerate your Kubernetes clusters with Varnish Caching
Accelerate your Kubernetes clusters with Varnish Caching
Thijs Feryn
 
Empowering NextGen Mobility via Large Action Model Infrastructure (LAMI): pav...
Empowering NextGen Mobility via Large Action Model Infrastructure (LAMI): pav...Empowering NextGen Mobility via Large Action Model Infrastructure (LAMI): pav...
Empowering NextGen Mobility via Large Action Model Infrastructure (LAMI): pav...
Thierry Lestable
 
Assuring Contact Center Experiences for Your Customers With ThousandEyes
Assuring Contact Center Experiences for Your Customers With ThousandEyesAssuring Contact Center Experiences for Your Customers With ThousandEyes
Assuring Contact Center Experiences for Your Customers With ThousandEyes
ThousandEyes
 
Knowledge engineering: from people to machines and back
Knowledge engineering: from people to machines and backKnowledge engineering: from people to machines and back
Knowledge engineering: from people to machines and back
Elena Simperl
 
Unsubscribed: Combat Subscription Fatigue With a Membership Mentality by Head...
Unsubscribed: Combat Subscription Fatigue With a Membership Mentality by Head...Unsubscribed: Combat Subscription Fatigue With a Membership Mentality by Head...
Unsubscribed: Combat Subscription Fatigue With a Membership Mentality by Head...
Product School
 
Key Trends Shaping the Future of Infrastructure.pdf
Key Trends Shaping the Future of Infrastructure.pdfKey Trends Shaping the Future of Infrastructure.pdf
Key Trends Shaping the Future of Infrastructure.pdf
Cheryl Hung
 
Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...
Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...
Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...
UiPathCommunity
 
From Daily Decisions to Bottom Line: Connecting Product Work to Revenue by VP...
From Daily Decisions to Bottom Line: Connecting Product Work to Revenue by VP...From Daily Decisions to Bottom Line: Connecting Product Work to Revenue by VP...
From Daily Decisions to Bottom Line: Connecting Product Work to Revenue by VP...
Product School
 
Mission to Decommission: Importance of Decommissioning Products to Increase E...
Mission to Decommission: Importance of Decommissioning Products to Increase E...Mission to Decommission: Importance of Decommissioning Products to Increase E...
Mission to Decommission: Importance of Decommissioning Products to Increase E...
Product School
 
Generating a custom Ruby SDK for your web service or Rails API using Smithy
Generating a custom Ruby SDK for your web service or Rails API using SmithyGenerating a custom Ruby SDK for your web service or Rails API using Smithy
Generating a custom Ruby SDK for your web service or Rails API using Smithy
g2nightmarescribd
 

Recently uploaded (20)

AI for Every Business: Unlocking Your Product's Universal Potential by VP of ...
AI for Every Business: Unlocking Your Product's Universal Potential by VP of ...AI for Every Business: Unlocking Your Product's Universal Potential by VP of ...
AI for Every Business: Unlocking Your Product's Universal Potential by VP of ...
 
The Future of Platform Engineering
The Future of Platform EngineeringThe Future of Platform Engineering
The Future of Platform Engineering
 
Bits & Pixels using AI for Good.........
Bits & Pixels using AI for Good.........Bits & Pixels using AI for Good.........
Bits & Pixels using AI for Good.........
 
Epistemic Interaction - tuning interfaces to provide information for AI support
Epistemic Interaction - tuning interfaces to provide information for AI supportEpistemic Interaction - tuning interfaces to provide information for AI support
Epistemic Interaction - tuning interfaces to provide information for AI support
 
FIDO Alliance Osaka Seminar: FIDO Security Aspects.pdf
FIDO Alliance Osaka Seminar: FIDO Security Aspects.pdfFIDO Alliance Osaka Seminar: FIDO Security Aspects.pdf
FIDO Alliance Osaka Seminar: FIDO Security Aspects.pdf
 
GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using Deplo...
GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using Deplo...GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using Deplo...
GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using Deplo...
 
Connector Corner: Automate dynamic content and events by pushing a button
Connector Corner: Automate dynamic content and events by pushing a buttonConnector Corner: Automate dynamic content and events by pushing a button
Connector Corner: Automate dynamic content and events by pushing a button
 
FIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdf
FIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdfFIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdf
FIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdf
 
Securing your Kubernetes cluster_ a step-by-step guide to success !
Securing your Kubernetes cluster_ a step-by-step guide to success !Securing your Kubernetes cluster_ a step-by-step guide to success !
Securing your Kubernetes cluster_ a step-by-step guide to success !
 
UiPath Test Automation using UiPath Test Suite series, part 3
UiPath Test Automation using UiPath Test Suite series, part 3UiPath Test Automation using UiPath Test Suite series, part 3
UiPath Test Automation using UiPath Test Suite series, part 3
 
Accelerate your Kubernetes clusters with Varnish Caching
Accelerate your Kubernetes clusters with Varnish CachingAccelerate your Kubernetes clusters with Varnish Caching
Accelerate your Kubernetes clusters with Varnish Caching
 
Empowering NextGen Mobility via Large Action Model Infrastructure (LAMI): pav...
Empowering NextGen Mobility via Large Action Model Infrastructure (LAMI): pav...Empowering NextGen Mobility via Large Action Model Infrastructure (LAMI): pav...
Empowering NextGen Mobility via Large Action Model Infrastructure (LAMI): pav...
 
Assuring Contact Center Experiences for Your Customers With ThousandEyes
Assuring Contact Center Experiences for Your Customers With ThousandEyesAssuring Contact Center Experiences for Your Customers With ThousandEyes
Assuring Contact Center Experiences for Your Customers With ThousandEyes
 
Knowledge engineering: from people to machines and back
Knowledge engineering: from people to machines and backKnowledge engineering: from people to machines and back
Knowledge engineering: from people to machines and back
 
Unsubscribed: Combat Subscription Fatigue With a Membership Mentality by Head...
Unsubscribed: Combat Subscription Fatigue With a Membership Mentality by Head...Unsubscribed: Combat Subscription Fatigue With a Membership Mentality by Head...
Unsubscribed: Combat Subscription Fatigue With a Membership Mentality by Head...
 
Key Trends Shaping the Future of Infrastructure.pdf
Key Trends Shaping the Future of Infrastructure.pdfKey Trends Shaping the Future of Infrastructure.pdf
Key Trends Shaping the Future of Infrastructure.pdf
 
Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...
Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...
Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...
 
From Daily Decisions to Bottom Line: Connecting Product Work to Revenue by VP...
From Daily Decisions to Bottom Line: Connecting Product Work to Revenue by VP...From Daily Decisions to Bottom Line: Connecting Product Work to Revenue by VP...
From Daily Decisions to Bottom Line: Connecting Product Work to Revenue by VP...
 
Mission to Decommission: Importance of Decommissioning Products to Increase E...
Mission to Decommission: Importance of Decommissioning Products to Increase E...Mission to Decommission: Importance of Decommissioning Products to Increase E...
Mission to Decommission: Importance of Decommissioning Products to Increase E...
 
Generating a custom Ruby SDK for your web service or Rails API using Smithy
Generating a custom Ruby SDK for your web service or Rails API using SmithyGenerating a custom Ruby SDK for your web service or Rails API using Smithy
Generating a custom Ruby SDK for your web service or Rails API using Smithy
 

Writing Swift code with great testability

  • 1. Writing Swift code with great testability ⌘ U +how became my new ⌘ R + @johnsundell or,
  • 2. ⌘ U +how became my new ⌘ R + Unit tests are a waste of time. It’s faster and easier to do manual testing, and focus my coding time on writing actual code. Unbox, Wrap, Hub Framework, etc… Integrating, re-building, manual testing⏳ Bugs, regressions, unwanted behavior"
  • 3. ⌘ U +how became my new ⌘ R + Automating testing = Focus on coding# $ Spotify app Hub Framework 20x faster compile times! Tests provide documentation of intent% Tests let me move faster, I don’t have to constantly run the app, but can instead verify most of its working parts in isolation, and make quick iterations.
  • 4. 3 tips on how to work with unit testing in
  • 5. 1 Design your code for testability
  • 6. What makes code easy to test? Unified Input/Output Injected dependencies State is kept local 1 Design your code for testability
  • 7. class FileLoader { static let shared = FileLoader() private let cache = Cache() func file(named fileName: String) throws -> File { if let cachedFile = cache.file(named: fileName) { return cachedFile } let bundle = Bundle.main guard let url = bundle.url(forResource: fileName, withExtension: nil) else { throw NSError(domain: "com.johnsundell.myapp.fileLoader", code: 7, userInfo: nil) } let data = try Data(contentsOf: url) let file = File(data: data) cache.cache(file: file, name: fileName) return file } } Unified Input/Output Injected dependencies State is kept local
  • 8. class FileLoader { static let shared = FileLoader() private let cache = Cache() func file(named fileName: String) throws -> File { if let cachedFile = cache.file(named: fileName) { return cachedFile } let bundle = Bundle.main guard let url = bundle.url(forResource: fileName, withExtension: nil) else { throw NSError(domain: "com.johnsundell.myapp.fileLoader", code: 7, userInfo: nil) } let data = try Data(contentsOf: url) let file = File(data: data) cache.cache(file: file, name: fileName) return file } } Unified Input/Output Injected dependencies State is kept local Unified Input/Output
  • 9. class FileLoader { static let shared = FileLoader() private let cache = Cache() func file(named fileName: String) throws -> File { if let cachedFile = cache.file(named: fileName) { return cachedFile } let bundle = Bundle.main guard let url = bundle.url(forResource: fileName, withExtension: nil) else { throw NSError(domain: "com.johnsundell.myapp.fileLoader", code: 7, userInfo: nil) } let data = try Data(contentsOf: url) let file = File(data: data) cache.cache(file: file, name: fileName) return file } } Unified Input/Output Injected dependencies State is kept local Unified Input/Output State is kept local
  • 10. class FileLoader { static let shared = FileLoader() private let cache = Cache() func file(named fileName: String) throws -> File { if let cachedFile = cache.file(named: fileName) { return cachedFile } let bundle = Bundle.main guard let url = bundle.url(forResource: fileName, withExtension: nil) else { throw NSError(domain: "com.johnsundell.myapp.fileLoader", code: 7, userInfo: nil) } let data = try Data(contentsOf: url) let file = File(data: data) cache.cache(file: file, name: fileName) return file } } Unified Input/Output Injected dependencies State is kept local Unified Input/Output State is kept local Injected dependencies
  • 11. class FileLoader { static let shared = FileLoader() private let cache = Cache() func file(named fileName: String) throws -> File { if let cachedFile = cache.file(named: fileName) { return cachedFile } let bundle = Bundle.main guard let url = bundle.url(forResource: fileName, withExtension: nil) else { throw NSError(domain: "com.johnsundell.myapp.fileLoader", code: 7, userInfo: nil) } let data = try Data(contentsOf: url) let file = File(data: data) cache.cache(file: file, name: fileName) return file } } Unified Input/Output Injected dependencies State is kept local Unified Input/Output State is kept local Injected dependencies enum FileLoaderError: Error { case invalidFileName(String) case invalidFileURL(URL) } Dedicated error type throw FileLoaderError.invalidFileName(fileName) do { let data = try Data(contentsOf: url) let file = File(data: data) cache.cache(file: file, name: fileName) return file } catch { throw FileLoaderError.invalidFileURL(url) } } } Unified error output Unified Input/Output
  • 12. class FileLoader { static let shared = FileLoader() private let cache = Cache() func file(named fileName: String) throws -> File { if let cachedFile = cache.file(named: fileName) { return cachedFile } let bundle = Bundle.main guard let url = bundle.url(forResource: fileName, withExtension: nil) else { throw NSError(domain: "com.johnsundell.myapp.fileLoader", code: 7, userInfo: nil) } let data = try Data(contentsOf: url) let file = File(data: data) cache.cache(file: file, name: fileName) return file } } Unified Input/Output Injected dependencies State is kept local Unified Input/Output State is kept local Injected dependencies enum FileLoaderError: Error { case invalidFileName(String) case invalidFileURL(URL) } throw FileLoaderError.invalidFileName(fileName) do { let data = try Data(contentsOf: url) let file = File(data: data) cache.cache(file: file, name: fileName) return file } catch { throw FileLoaderError.invalidFileURL(url) } } } Unified Input/Output State is kept local
  • 13. class FileLoader { private let cache = Cache() Unified Input/Output Injected dependencies State is kept local Unified Input/Output State is kept local Injected dependencies Unified Input/Output State is kept local private let bundle: Bundle init(cache: Cache self.cache = cache self.bundle = bundle } , bundle: Bundle) { func file(named fileName: String) throws -> File { if let cachedFile = cache.file(named: fileName) { return cachedFile } let bundle = Bundle.main guard let url = bundle.url(forResource: fileName, withExtension: nil) else { throw FileLoaderError.invalidFileName(fileName) } do { let data = try Data(contentsOf: url) let file = File(data: data) cache.cache(file: file, name: fileName) return file } catch { throw FileLoaderError.invalidFileURL(url) } } } Dependency injection (with defaults) = .init(), bundle: Bundle = .main) { bundle Using injected dependencies Injected dependencies
  • 14. Let’s write a test! & 1 Design your code for testability
  • 15. 2 Use access control to create clear API boundaries
  • 16. 2 Use access control to create clear API boundaries APIInput Asserts Unit test Code Integration test
  • 17. 2 Use access control to create clear API boundaries private fileprivate internal public open
  • 18. 2 Use access control to create clear API boundaries public class SendMessageViewController: UIViewController { public var recipients: [User] public var title: String public var message: String public var recipientsPicker: UserPickerView? public var titleTextField: UITextField? public var messageTextField: UITextField? }
  • 19. 2 Use access control to create clear API boundaries public class SendMessageViewController: UIViewController { private var recipients: [User] private var title: String private var message: String private var recipientsPicker: UserPickerView? private var titleTextField: UITextField? private var messageTextField: UITextField? }
  • 20. 2 Use access control to create clear API boundaries } func update(recepients: [User]? = nil, title: String? = nil, message: String? = nil) { if let recepients = recepients { self.recipients = recepients } // Same for title & message } public class SendMessageViewController: UIViewController { private var recipients: [User] private var title: String private var message: String private var recipientsPicker: UserPickerView? private var titleTextField: UITextField? private var messageTextField: UITextField? Single API entry point
  • 21. 2 Use access control to create clear API boundaries Frameworks
  • 22. 2 Use access control to create clear API boundaries Input Asserts Unit test Integration test
  • 23. 2 Use access control to create clear API boundaries App Models Views Logic
  • 24. 2 Use access control to create clear API boundaries App Models Views LogicKit Kit ($ brew install swiftplate)
  • 25. 3 Avoid mocks to avoid getting tied down into implementation details
  • 26. 3 Avoid mocks to avoid getting tied down into implementation details ' Mocks are “fake” objects that are used in tests to be able to assert that certain things happen as expected.
  • 27. // Objective-C (using OCMockito) NSBundle *bundle = mock([NSBundle class]); [given([bundle pathForResource:anything() ofType:anything()]) willReturn:@“path”]; FileLoader *fileLoader = [[FileLoader alloc] initWithBundle:bundle]; XCTAssertNotNil([fileLoader fileNamed:@"file"]); // Swift (partial mocking) class MockBundle: Bundle { var mockPath: String? override func path(forResource name: String?, ofType ext: String?) -> String? { return mockPath } } let bundle = MockBundle() bundle.mockPath = "path" let fileLoader = FileLoader(bundle: bundle) XCTAssertNotNil(fileLoader.file(named: "file")) // Swift (no mocking) let bundle = Bundle(for: type(of: self)) let fileLoader = FileLoader(bundle: bundle) XCTAssertNotNil(fileLoader.file(named: "file")) 3 Avoid mocks to avoid getting tied down into implementation details
  • 28. class ImageLoader { func loadImage(named imageName: String) -> UIImage? { return UIImage(named: imageName) } } class ImageViewController: UIViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) imageView.image = imageLoader.loadImage(named: imageName) } } // Test using mocking class ImageViewControllerTests: XCTestCase { func testImageLoadedOnViewWillAppear() { class MockImageLoader: ImageLoader { private(set) var loadedImageNames = [String]() override func loadImage(named name: String) -> UIImage { loadedImageNames.append(name) return UIImage() } } let imageLoader = MockImageLoader() let vc = ImageViewController(imageLoader: imageLoader) vc.imageName = "image" vc.viewWillAppear(false) XCTAssertEqual(imageLoader.loadedImageNames, ["image"]) } } Manually implemented, partial mock Asserting that an image was loaded by verifying what our mock captured Capture loaded image names 3 Avoid mocks to avoid getting tied down into implementation details
  • 29. class ImageLoader { private let preloadedImages: [String : UIImage] init(preloadedImages: [String : UIImage] = [:]) { self.preloadedImages = preloadedImages } func loadImage(named imageName: String) -> UIImage? { if let preloadedImage = preloadedImages[imageName] { return preloadedImage } return UIImage(named: imageName) } } // Test without mocking class ImageViewControllerTests: XCTestCase { func testImageLoadedOnViewWillAppear() { let image = UIImage() let imageLoader = ImageLoader(images: ["image" : image]) let vc = ImageViewController(imageLoader: imageLoader) vc.imageName = "image" vc.viewWillAppear(false) XCTAssertEqual(vc.image, image) } } Optionally enable preloaded images to be injected Use preloaded image if any exists Compare against actually rendered image, instead of relying on mock capturing 3 Avoid mocks to avoid getting tied down into implementation details
  • 30. However, sometimes you do need mocks, so let’s make it easy to use them! ( 3 Avoid mocks to avoid getting tied down into implementation details
  • 31. To summarize 1 Design your code for testability 2 Use access control to create clear API boundaries 3 Avoid mocks to avoid getting tied down into implementation details
  • 32. No tests? ) No problem! * Just start somewhere ( Set goals for test coverage &