Technology has enabled the
jukebox to be so much more
• ~6 Million Users • ~42 Million Check-Ins • ~240 Million plays • ~1.4K Ratings • 4.7 stars in the app store
Mobile App
We are hiring!
�1
TESTING YOUR SWIFT APPLICATION WITH CUCKOO
WHY TESTING?▸ Bug detection
▸ Improve debugging process
▸ Quality of code
▸ Reduce cost
▸ Refactoring
�3
TESTING YOUR SWIFT APPLICATION WITH CUCKOO
EXCUSES▸ Not enough time
▸ Technical Debt
▸ Lack of Knowledge
▸ Lack of Value
▸ Absence of Continuous Integration
�4
TESTING YOUR SWIFT APPLICATION WITH CUCKOO
BASICS OF UNIT TESTING�5
TESTABLE UNIT
input 0 … …
input N
Expected result
Failure}
▸ Fast
▸ No external dependencies (ie. remote configuration)
▸ No communication with APIs (Integration Tests)
▸ Easy to write and understand
▸ Reliability (the test should always succeed)
*Design Flaw *Bug *Dependency change
Given When Then
TESTING YOUR SWIFT APPLICATION WITH CUCKOO
DESIGNING AND ARCHITECTING FOR TESTING�7
PRESENTER VIEW
API BROKER
Module
Event Handler User Interface
Broker
IMAGE EDITOR
ImageUtils
TESTING YOUR SWIFT APPLICATION WITH CUCKOO �8
PRESENTER VIEW
API BROKER
Module
Event Handler User Interface
Broker
Testable
IMAGE EDITOR
DESIGNING AND ARCHITECTING FOR TESTING
ImageUtils
TESTING YOUR SWIFT APPLICATION WITH CUCKOO
MOCKING AND STUBBING�9
PRESENTER VIEW
API BROKER
Module
Event Handler User Interface
Broker
Mocks and Stubs
Mocks and Stubs
IMAGE EDITOR
ImageUtils
TESTING YOUR SWIFT APPLICATION WITH CUCKOO
MOCKING AND STUBBING�10
Source: medium.com
TESTING YOUR SWIFT APPLICATION WITH CUCKOO
DESIGNING AND ARCHITECTING FOR TESTING�11
protocol UserInterface: class { var eventHandler: EventHandler? { get set } func update(image: UIImage?) func setActive(filter: FilterType) func resetFilters()}
protocol EventHandler: class { func activate(filter: FilterType) func changeImage() func reset() func save()}
protocol Broker { func fetchImage(completion: (UIImage?) -> Void) func save(image: UIImage, success: (() -> Void)?, failure: ((Error) -> Void)?)}
Testable
protocol ImageUtils { func apply(filter: FilterType, input: CGImage, completion: @escaping (UIImage?) -> Void)}
TESTING YOUR SWIFT APPLICATION WITH CUCKOO
IMPLEMENTATION�12
class Presenter { var userInterface: UserInterface? var broker: Broker? var imageEditor: ImageEditor?}
extension Presenter: EventHandler { func activate(filter: FilterType) {…} func changeImage() {…} func reset() {…} func save() {…}}
} Dependencies
TESTING YOUR SWIFT APPLICATION WITH CUCKOO
WHAT IS CUCKOO?▸ Created due to lack of a proper Swift mocking
framework
▸ Similar to Mockito
▸ Due to a lack of proper reflection in Swift, Cuckoo use a compile-time code generation
▸ Makes it easier to create Mocks and Stubs
▸ Repo: https://github.com/Brightify/Cuckoo
�13
TESTING YOUR SWIFT APPLICATION WITH CUCKOO
INSTALLING CUCKOO
carthage update --platform iOS
github "SwiftKit/Cuckoo"
▸ Add framework to Cartfile
▸ Run Carthage
▸ Add Framework to the Test target on Build Phases
�14
TESTING YOUR SWIFT APPLICATION WITH CUCKOO
USING CUCKOO▸ Add Run Script phase to generate mocks
# Define output file.OUTPUT_FILE="$PROJECT_DIR/${PROJECT_NAME}Tests/GeneratedMocks.swift"echo "Generated Mocks File = $OUTPUT_FILE"
# Define input directory.INPUT_DIR="${PROJECT_DIR}/${PROJECT_NAME}"echo "Mocks Input Directory = $INPUT_DIR"
# Generate mock files, include as many input files as you'd like."Carthage/Checkouts/Cuckoo/run" generate --testable "$PROJECT_NAME" \--output "${OUTPUT_FILE}" \"$INPUT_DIR/FileName1.swift" \"$INPUT_DIR/FileName2.swift" \"$INPUT_DIR/FileName3.swift"# ... and so forth, the last line should never end with a backslash
# After running once, locate `GeneratedMocks.swift` and drag it into your Xcode test target group.
�15
TESTING YOUR SWIFT APPLICATION WITH CUCKOO
USING CUCKOO�16
PRESENTER
Module
Event Handler User Interface
Broker
Testable
VIEW
API BROKER IMAGE EDITOR
ImageUtils
VIEW
API BROKER IMAGE EDITOR
TESTING YOUR SWIFT APPLICATION WITH CUCKOO
USING CUCKOO�17
PRESENTER MOCK VIEW
MOCK API BROKER
Module
Event Handler User Interface
Broker
Testable
MOCK IMAGE EDITOR
ImageUtils
TESTING YOUR SWIFT APPLICATION WITH CUCKOO
USING CUCKOO
let presenter = Presenter() // Class to test
// Dependenciespresenter.userInterface = MockUserInterface()presenter.broker = MockBroker()presenter.imageEditor = MockImageEditor()
▸ Initialization
▸ Spy
// Spies only work with mocked classes not protocols let spy = MockClass().withEnabledSuperclassSpy()
�18
TESTING YOUR SWIFT APPLICATION WITH CUCKOO
TEST #1 - RESET FILTERS�19
▸ Tap on the Reset Button
▸ The filter swift turn off
▸ The image is updated to the original
TESTING YOUR SWIFT APPLICATION WITH CUCKOO �20
// Givenlet presenter = Presenter()presenter.userInterface = MockUserInterface()
func testResetFilter() {
guard let mockUserInterface = presenter.userInterface as? MockUserInterface else { XCTFail(); return }
var filtersWereReset = falsevar userInterfaceWasUpdated = false
// Whenstub(mockUserInterface) { stub in when(stub.resetFilters()).then { filtersWereReset = true }}stub(mockUserInterface) { stub in when(stub.update(image: any())).then { _ in userInterfaceWasUpdated = true }}
presenter.reset()// ThenXCTAssertTrue(filtersWereReset && userInterfaceWasUpdated)
}
TESTING YOUR SWIFT APPLICATION WITH CUCKOO
MATCHERS�21
• Bool• String• Float• Double• Character• Int• Int8•
You can mock any object that conforms to the Matchable protocol. These basic values are extended to conform to Matchable:
or…public func any<T>(_ type: T.Type = default) -> Cuckoo.ParameterMatcher<T>
public func anyClosure<IN, OUT>() -> Cuckoo.ParameterMatcher<(IN) -> OUT>
public func anyInt() -> Cuckoo.ParameterMatcher<Int?>
public func anyOptionalThrowingClosure<IN, OUT>() -> Cuckoo.ParameterMatcher<(((IN)) throws -> OUT)?>
public func anyString() -> Cuckoo.ParameterMatcher<String>
• Int16• Int32• Int64• UInt• UInt8• UInt16• UInt32• UInt64
TESTING YOUR SWIFT APPLICATION WITH CUCKOO
TEST #2- APPLY FILTERS�22
▸ Activate on the filters switch to apply it
▸ The filter is applied over the original image
▸ The image is updated with the filtered version
TESTING YOUR SWIFT APPLICATION WITH CUCKOO �23
func testSepiaFilterApplication() {
}
guard let mockEditor = presenter.imageEditor as? MockImageEditor, let mockUserInterface = presenter.userInterface as? MockUserInterface else { XCTFail(); return }
// Givenlet presenter = Presenter() presenter.userInterface = MockUserInterface()presenter.imageEditor = MockImageEditor()
var filterWasApplied = falsevar properFilterSwitchWasActivated = falsevar userInterfaceWasUpdated = false
let testImage = UIImage(named: "city1")
presenter.originalImage = testImage
. . .
TESTING YOUR SWIFT APPLICATION WITH CUCKOO �24
func testSepiaFilterApplication() {
}
. . . // Whenstub(mockUserInterface) { stub in stub.setActive(filter: any()).then { filter in properFilterSwitchWasActivated = (filter == .sepia) }}
stub(mockEditor) { stub in stub.apply(filter: any(), input: any(), completion: any()).then { filter, inputImage, completion in filterWasApplied = (filter == .sepia) && (inputImage == testImage?.cgImage) completion(testImage) verify(mockUserInterface).update(image: any()) }}
stub(mockUserInterface) { stub in stub.update(image: any()).then { imageToUpdate in userInterfaceWasUpdatedWithTestImage = (imageToUpdate == testImage) }}
presenter.activate(filter: .sepia)
// ThenXCTAssertTrue(filterWasApplied && userInterfaceWasUpdated && properFilterSwitchWasActivated)
verify(mockUserInterface).setActive(filter: any())
TESTING YOUR SWIFT APPLICATION WITH CUCKOO
SAMPLE APP�25
https://github.com/jotajota05/cuckoo-example
TESTING YOUR SWIFT APPLICATION WITH CUCKOO
CONCLUSION�26
▸ Design and architecture are key when adding Unit Tests
▸ Separation of responsibility
▸ Small testable units
▸ Easier Mocking and Stubbing in Swift with Cuckoo framework