SlideShare una empresa de Scribd logo
1 de 69
Descargar para leer sin conexión
!
The Testing Games: Mocking, yay!
And may the mocks be ever in your favour
I like testing
It allows me to prove my code works
But I’m also quite lazy
And I know I’m not alone
Improving our own tests
Improving our own tests
1. Create a set of guidelines
Improving our own tests
1. Create a set of guidelines
2. Update the testing framework’s DSL (we use Quick)
Improving our own tests
1. Create a set of guidelines
2. Update the testing framework’s DSL (we use Quick)
3. Change the way we think about mocking
class AuthenticationServiceTests: QuickSpec {
override func spec() {
describe("The Service") {
it("Should log in") {
}
}
}
}
class AuthenticationServiceTests: QuickSpec {
override func spec() {
describe("The Service") {
it("Should log in") {
}
}
}
}
This isn’t too bad, right?
class AuthenticationServiceTests: QuickSpec {
override func spec() {
describe("The Service") {
it("Should log in") {
let service = AuthenticationService(api: self)
service.loginUser(withUsername: "Donny", password: "password") { user, error in
expect(user?.name) == "Donny"
expect(user?.id) == 1
}
}
}
}
}
extension AuthenticationServiceTests: AuthenticationAPi {
func loginUser(withUsername username: String, password: String, callback: (Data?, Error?) -> Void) {
let json = """
{"status": "ok", "user": {"id": 1, "name": “Donny"}}
""".data(using: .utf8)
callback(json, nil)
}
}
This is hard to read!
class AuthenticationServiceTests: QuickSpec {
override func spec() {
describe("The Service") {
it("Should log in") {
let service = AuthenticationService(api: self)
service.loginUser(withUsername: "Donny", password: "password") { user, error in
expect(user?.name) == "Donny"
expect(user?.id) == 1
}
}
}
}
}
extension AuthenticationServiceTests: AuthenticationAPi {
func loginUser(withUsername username: String, password: String, callback: (Data?, Error?) -> Void) {
let json = """
{"status": "ok", "user": {"id": 1, "name": “Donny"}}
""".data(using: .utf8)
callback(json, nil)
}
}
All values are hardcoded
class AuthenticationServiceTests: QuickSpec {
override func spec() {
describe("The Service") {
it("Should log in") {
let service = AuthenticationService(api: self)
service.loginUser(withUsername: "Donny", password: "password") { user, error in
expect(user?.name) == "Donny"
expect(user?.id) == 1
}
}
}
}
}
extension AuthenticationServiceTests: AuthenticationAPi {
func loginUser(withUsername username: String, password: String, callback: (Data?, Error?) -> Void) {
let json = """
{"status": "ok", "user": {"id": 1, "name": “Donny"}}
""".data(using: .utf8)
callback(json, nil)
}
}
The json is inline
class AuthenticationServiceTests: QuickSpec {
override func spec() {
describe("The Service") {
it("Should log in") {
let service = AuthenticationService(api: self)
service.loginUser(withUsername: "Donny", password: "password") { user, error in
expect(user?.name) == "Donny"
expect(user?.id) == 1
}
}
}
}
}
extension AuthenticationServiceTests: AuthenticationAPi {
func loginUser(withUsername username: String, password: String, callback: (Data?, Error?) -> Void) {
let json = """
{"status": "ok", "user": {"id": 1, "name": “Donny"}}
""".data(using: .utf8)
callback(json, nil)
}
}
let passwordError = """

{"status": "error", "code": 1001, "message": "Wrong password"}

"""
let passwordError = """

{"status": "error", "code": 1001, "message": "Wrong password"}

"""
let passwordError = """

{"status": "error", "code": 1001, "message": "Wrong password"}

"""
let usernameError = """

{"status": "error", "code": 1002, "message": "User not found"}

"""
let passwordError = """

{"status": "error", "code": 1001, "message": "Wrong password"}

"""
let usernameError = """

{"status": "error", "code": 1002, "message": "User not found"}

"""
let passwordError = """

{"status": "error", "code": 1001, "message": "Wrong password"}

"""
let usernameError = """

{"status": "error", "code": 1002, "message": "User not found"}

"""
let validationError = """

{"status": "error", "code": 1003, "message": "Username or password missing"}

"""
let passwordError = """

{"status": "error", "code": 1001, "message": "Wrong password"}

"""
let usernameError = """

{"status": "error", "code": 1002, "message": "User not found"}

"""
let validationError = """

{"status": "error", "code": 1003, "message": "Username or password missing"}

"""
let passwordError = """

{"status": "error", "code": 1001, "message": "Wrong password"}

"""
let usernameError = """

{"status": "error", "code": 1002, "message": "User not found"}

"""
let validationError = """

{"status": "error", "code": 1003, "message": "Username or password missing"}

"""
let serverError = """

{"status": "error", "code": 1004, "message": "Something went wrong on the server"}

"""
let passwordError = """

{"status": "error", "code": 1001, "message": "Wrong password"}

"""
let passwordError = """

{"status": "error", "code": 1001, "message": "Wrong password"}

"""
let passwordError = """

{"status": "error", "code": 1001, "message": "Wrong password"}

"""
let usernameError = """

{"status": "error", "code": 1002, "message": "User not found"}

"""
let passwordError = """

{"status": "error", "code": 1001, "message": "Wrong password"}

"""
let usernameError = """

{"status": "error", "code": 1002, "message": "User not found"}

"""
let passwordError = """

{"status": "error", "code": 1001, "message": "Wrong password"}

"""
let usernameError = """

{"status": "error", "code": 1002, "message": "User not found"}

"""
let validationError = """

{"status": "error", "code": 1003, "message": "Username or password missing"}

"""
let passwordError = """

{"status": "error", "code": 1001, "message": "Wrong password"}

"""
let usernameError = """

{"status": "error", "code": 1002, "message": "User not found"}

"""
let validationError = """

{"status": "error", "code": 1003, "message": "Username or password missing"}

"""
let passwordError = """

{"status": "error", "code": 1001, "message": "Wrong password"}

"""
let usernameError = """

{"status": "error", "code": 1002, "message": "User not found"}

"""
let validationError = """

{"status": "error", "code": 1003, "message": "Username or password missing"}

"""
let serverError = """

{"status": "error", "code": 1004, "message": "Something went wrong on the server"}

"""
let json = """
{
"_id": "5b05709156da3c5e1a933dce",
"index": 0,
"guid": "ca6507f6-f462-478e-ad4d-f9e46f9ee88a",
"isActive": true,
"balance": "$3,125.88",
"picture": "http://placehold.it/32x32",
"age": 38,
"eyeColor": "green",
"name": {
"first": "Dunlap",
"last": "Lott"
},
"company": "FUELTON",
"email": "dunlap.lott@fuelton.biz",
"phone": "+1 (888) 407-2414",
"address": "883 Nixon Court, Wolcott, Marshall Islands, 2469",
"about": "Dolor tempor ullamco deserunt tempor sunt esse irure ut fugiat. Irure occaecat fugiat deserunt
ad. Ipsum laborum excepteur excepteur dolore qui aliqua. Ad tempor culpa commodo laborum laboris ut
amet elit est cupidatat consectetur nostrud consequat.",
"registered": "Saturday, August 5, 2017 12:57 PM",
let json = """
{
"_id": "5b05709156da3c5e1a933dce",
"index": 0,
"guid": "ca6507f6-f462-478e-ad4d-f9e46f9ee88a",
"isActive": true,
"balance": "$3,125.88",
"picture": "http://placehold.it/32x32",
"age": 38,
"eyeColor": "green",
"name": {
"first": "Dunlap",
"last": "Lott"
},
"company": "FUELTON",
"email": "dunlap.lott@fuelton.biz",
"phone": "+1 (888) 407-2414",
"address": "883 Nixon Court, Wolcott, Marshall Islands, 2469",
"about": "Dolor tempor ullamco deserunt tempor sunt esse irure ut fugiat. Irure occaecat fugiat deserunt
ad. Ipsum laborum excepteur excepteur dolore qui aliqua. Ad tempor culpa commodo laborum laboris ut
amet elit est cupidatat consectetur nostrud consequat.",
"registered": "Saturday, August 5, 2017 12:57 PM",
😒
Duh! You should use fixtures
let passwordError = ErrorFixture(type: .password)
let usernameError = ErrorFixture(type: .username)
let validationError = ErrorFixture(type: .validation)
let serverError = ErrorFixture(type: .server)
extension AuthenticationServiceTests: AuthenticationAPI {
func loginUser(withUsername username: String, password: String, callback: (Data?, Error?) -> Void) {
let json = """
{"status": "ok", "user": {"id": 1, "name": "Donny"}
""".data(using: .utf8)
callback(json, nil)
}
}
extension AuthenticationServiceTests: AuthenticationAPI {
func loginUser(withUsername username: String, password: String, callback: (Data?, Error?) -> Void) {
let fixture = AuthenticationFixture(type: .success)
callback(fixture.data, nil)
}
}
class AuthenticationServiceTests: QuickSpec {
override func spec() {
describe("The Service") {
it("Should log in") {
let service = AuthenticationService(api: self)
service.loginUser(withUsername: "Donny", password: "password") { user, error in
expect(user?.name) == "Donny"
expect(user?.id) == 1
}
}
}
}
}
We just made the hardcoded values worse…
Swift 4.0, to the rescue
Swift 4.0, to the rescue
{JSON} Decodable User
Swift 4.0, to the rescue
{JSON} Decodable User
Why not do this?
Swift 4.0, to the rescue
{JSON} Decodable User
Why not do this?
{JSON} Encodable User
describe("The Authentication Service") {
it("Should log in a user") {
let mockUser = User(name: "Donny", id: 1)
let userData = try! JSONEncoder().encode(mockUser)
let authenticationApi = MockAuthenticationApi(responseData: userData, error: nil)
let service = AuthenticationService(api: authenticationApi)
service.loginUser(withUsername: "Donny", password: "password") { user, error in
expect(user?.name) == mockUser.name
expect(user?.id) == mockUser.id
}
}
}
Now we’re getting somewhere!
struct Prize: Codable {
enum PrizeType: Int, Codable {
case regular = 0, main = 1
}
let id: Int
let type: PrizeType
let label: String
}
struct Play: Codable {
let id: Int
let acquiredPrizes: [Prize]
}
struct Game: Codable {
let id: Int
let availablePrizes: [Prize]
let name: String
let closeDate: Date
let plays: [Play]
let maxPlayCount: Int
}
struct PlayResponse {
let game: Game
let currentPlay: Play
}
But we’re not quite there
The final steps
Mockable
protocol Mockable: Codable {
associatedtype BaseType: Decodable
}
extension Mockable {
var data: Data? {
return try? JSONEncoder().encode(self)
}
var baseType: BaseType? {
guard let data = self.data
else { return nil }
return try? JSONDecoder().decode(BaseType.self, from: data)
}
}
Mockable
protocol Mockable: Codable {
associatedtype BaseType: Decodable
}
extension Mockable {
var data: Data? {
return try? JSONEncoder().encode(self)
}
var baseType: BaseType? {
guard let data = self.data
else { return nil }
return try? JSONDecoder().decode(BaseType.self, from: data)
}
}
Mockable
protocol Mockable: Codable {
associatedtype BaseType: Decodable
}
extension Mockable {
var data: Data? {
return try? JSONEncoder().encode(self)
}
var baseType: BaseType? {
guard let data = self.data
else { return nil }
return try? JSONDecoder().decode(BaseType.self, from: data)
}
}
Mock all the things!
class MockPrize: Mockable {
typealias BaseType = Prize
enum PrizeType: Int, Codable {
case regular = 0, main = 1
}
var id = 0
var type = PrizeType.regular
var label = "Default Label"
}
class MockPlay: Mockable {
typealias BaseType = Play
var id = 0
var acquiredPrizes = [MockPrize]()
}
class MockGame: Mockable {
typealias BaseType = Game
var id = 0
var availablePrizes = [MockPrize]()
var name = "Default game name"
var closeDate = Date()
var plays = [MockPlay]()
var maxPlayCount = 1
}
class MockPlayResponse: Mockable {
typealias BaseType = PlayResponse
var game = MockGame()
var currentPlay = MockPlay()
}
Mock all the things!
I know… it would be great if you could skip this!
class MockPrize: Mockable {
typealias BaseType = Prize
enum PrizeType: Int, Codable {
case regular = 0, main = 1
}
var id = 0
var type = PrizeType.regular
var label = "Default Label"
}
class MockPlay: Mockable {
typealias BaseType = Play
var id = 0
var acquiredPrizes = [MockPrize]()
}
class MockGame: Mockable {
typealias BaseType = Game
var id = 0
var availablePrizes = [MockPrize]()
var name = "Default game name"
var closeDate = Date()
var plays = [MockPlay]()
var maxPlayCount = 1
}
class MockPlayResponse: Mockable {
typealias BaseType = PlayResponse
var game = MockGame()
var currentPlay = MockPlay()
}
What does this give us?
What does this give us?
• Mutable “clones” of the models
What does this give us?
• Mutable “clones” of the models
• (Sensible) Default values
What does this give us?
• Mutable “clones” of the models
• (Sensible) Default values
• Objects that are ready to be encoded to JSON
What does this give us?
• Mutable “clones” of the models
• (Sensible) Default values
• Objects that are ready to be encoded to JSON
• Some spare time because we don’t need to collect mocks for each
configuration
What does this give us?
• Mutable “clones” of the models
• (Sensible) Default values
• Objects that are ready to be encoded to JSON
• Some spare time because we don’t need to collect mocks for each
configuration
• Typo-free comparisons
Now this is MotherMocking nice!
describe("When playing a game and the server returns no prizes") {
let gameData = MockGame().data
let gameApi = MockGameApi(preparedData: gameData, error: nil)
let gameService = GameService(api: gameApi)
it("The parsed model should not contain prizes") {
gameService.play { game, error in
expect(game?.plays.first?.acquiredPrizes.count) == 0
}
}
}
Now this is MotherMocking nice!
describe("When playing a game and the server returns a main prize") {
let game = MockGame()
let play = MockPlay()
let prize = MockPrize()
prize.type = .main
play.acquiredPrizes = [prize]
game.plays = [play]
let gameApi = MockGameApi(preparedData: game.data, error: nil)
let gameService = GameService(api: gameApi)
it("The parsed model should contain a single main prize") {
gameService.play { game, error in
expect(game?.plays.first?.acquiredPrizes.count) == 1
expect(game?.plays.first?.acquiredPrizes.first?.type) == .main
}
}
}
Easier configuration of mocks
encourages testing more cases
What is not easier with this approach?
What is not easier with this approach?
• It’s hard to test what your code does if you get malformed json
What is not easier with this approach?
• It’s hard to test what your code does if you get malformed json
• You can’t purposefully omit certain properties (unless you make a special
mock class for that case)
What is not easier with this approach?
• It’s hard to test what your code does if you get malformed json
• You can’t purposefully omit certain properties (unless you make a special
mock class for that case)
• You still need a single source of truth for what data goes into a model. You
could coordinate some sort of setup with the back-end team so you can
fetch a blueprint for each model
Some final notes
Some final notes
• It’s easier to mock responses if your api services return Data instead of
models
Some final notes
• It’s easier to mock responses if your api services return Data instead of
models
• Make smaller services so you can easily create focussed mocks for them
Some final notes
• It’s easier to mock responses if your api services return Data instead of
models
• Make smaller services so you can easily create focussed mocks for them
• Your tests shouldn’t rely on a network connection
Some final notes
• It’s easier to mock responses if your api services return Data instead of
models
• Make smaller services so you can easily create focussed mocks for them
• Your tests shouldn’t rely on a network connection
• This (obviously) is not a silver bullet
Some final notes
• It’s easier to mock responses if your api services return Data instead of
models
• Make smaller services so you can easily create focussed mocks for them
• Your tests shouldn’t rely on a network connection
• This (obviously) is not a silver bullet
• Large or complex configurations can still be fed to Mockable objects since
they conform to Codable
#

Más contenido relacionado

La actualidad más candente

Is HTML5 Ready? (workshop)
Is HTML5 Ready? (workshop)Is HTML5 Ready? (workshop)
Is HTML5 Ready? (workshop)
Remy Sharp
 

La actualidad más candente (18)

Is HTML5 Ready? (workshop)
Is HTML5 Ready? (workshop)Is HTML5 Ready? (workshop)
Is HTML5 Ready? (workshop)
 
Линзы - комбинаторная манипуляция данными Александр Гранин Dev2Dev v2.0 30.05...
Линзы - комбинаторная манипуляция данными Александр Гранин Dev2Dev v2.0 30.05...Линзы - комбинаторная манипуляция данными Александр Гранин Dev2Dev v2.0 30.05...
Линзы - комбинаторная манипуляция данными Александр Гранин Dev2Dev v2.0 30.05...
 
The (unknown) collections module
The (unknown) collections moduleThe (unknown) collections module
The (unknown) collections module
 
MongoDB .local Paris 2020: La puissance du Pipeline d'Agrégation de MongoDB
MongoDB .local Paris 2020: La puissance du Pipeline d'Agrégation de MongoDBMongoDB .local Paris 2020: La puissance du Pipeline d'Agrégation de MongoDB
MongoDB .local Paris 2020: La puissance du Pipeline d'Agrégation de MongoDB
 
MongoDB .local Munich 2019: Tips and Tricks++ for Querying and Indexing MongoDB
MongoDB .local Munich 2019: Tips and Tricks++ for Querying and Indexing MongoDBMongoDB .local Munich 2019: Tips and Tricks++ for Querying and Indexing MongoDB
MongoDB .local Munich 2019: Tips and Tricks++ for Querying and Indexing MongoDB
 
Perl object ?
Perl object ?Perl object ?
Perl object ?
 
BDD - Behavior Driven Development Webapps mit Groovy Spock und Geb
BDD - Behavior Driven Development Webapps mit Groovy Spock und GebBDD - Behavior Driven Development Webapps mit Groovy Spock und Geb
BDD - Behavior Driven Development Webapps mit Groovy Spock und Geb
 
Getting started with Elasticsearch and .NET
Getting started with Elasticsearch and .NETGetting started with Elasticsearch and .NET
Getting started with Elasticsearch and .NET
 
BDD, ATDD, Page Objects: The Road to Sustainable Web Testing
BDD, ATDD, Page Objects: The Road to Sustainable Web TestingBDD, ATDD, Page Objects: The Road to Sustainable Web Testing
BDD, ATDD, Page Objects: The Road to Sustainable Web Testing
 
Url programming
Url programmingUrl programming
Url programming
 
MongoDB World 2016: Deciphering .explain() Output
MongoDB World 2016: Deciphering .explain() OutputMongoDB World 2016: Deciphering .explain() Output
MongoDB World 2016: Deciphering .explain() Output
 
Event handling using jQuery
Event handling using jQueryEvent handling using jQuery
Event handling using jQuery
 
To infinity and beyond
To infinity and beyondTo infinity and beyond
To infinity and beyond
 
Mongo db mug_2012-02-07
Mongo db mug_2012-02-07Mongo db mug_2012-02-07
Mongo db mug_2012-02-07
 
MongoDB Performance Tuning
MongoDB Performance TuningMongoDB Performance Tuning
MongoDB Performance Tuning
 
Round pegs and square holes
Round pegs and square holesRound pegs and square holes
Round pegs and square holes
 
Tame Accidental Complexity with Ruby and MongoMapper
Tame Accidental Complexity with Ruby and MongoMapperTame Accidental Complexity with Ruby and MongoMapper
Tame Accidental Complexity with Ruby and MongoMapper
 
Conquering JSONB in PostgreSQL
Conquering JSONB in PostgreSQLConquering JSONB in PostgreSQL
Conquering JSONB in PostgreSQL
 

Similar a The Testing Games: Mocking, yay!

Opa presentation at GamesJs
Opa presentation at GamesJsOpa presentation at GamesJs
Opa presentation at GamesJs
Henri Binsztok
 
Nodejs functional programming and schema validation lightning talk
Nodejs   functional programming and schema validation lightning talkNodejs   functional programming and schema validation lightning talk
Nodejs functional programming and schema validation lightning talk
Deepank Gupta
 
JSON Schema: Your API's Secret Weapon
JSON Schema: Your API's Secret WeaponJSON Schema: Your API's Secret Weapon
JSON Schema: Your API's Secret Weapon
Pete Gamache
 
Third Party Auth in WebObjects
Third Party Auth in WebObjectsThird Party Auth in WebObjects
Third Party Auth in WebObjects
WO Community
 
Html basics 11 form validation
Html basics 11 form validationHtml basics 11 form validation
Html basics 11 form validation
H K
 

Similar a The Testing Games: Mocking, yay! (20)

How to test complex SaaS applications - The family july 2014
How to test complex SaaS applications - The family july 2014How to test complex SaaS applications - The family july 2014
How to test complex SaaS applications - The family july 2014
 
Opa presentation at GamesJs
Opa presentation at GamesJsOpa presentation at GamesJs
Opa presentation at GamesJs
 
10 Rules for Safer Code
10 Rules for Safer Code10 Rules for Safer Code
10 Rules for Safer Code
 
10 Rules for Safer Code [Odoo Experience 2016]
10 Rules for Safer Code [Odoo Experience 2016]10 Rules for Safer Code [Odoo Experience 2016]
10 Rules for Safer Code [Odoo Experience 2016]
 
Amazon Web Services Security
Amazon Web Services SecurityAmazon Web Services Security
Amazon Web Services Security
 
Nodejs functional programming and schema validation lightning talk
Nodejs   functional programming and schema validation lightning talkNodejs   functional programming and schema validation lightning talk
Nodejs functional programming and schema validation lightning talk
 
Security Slicing for Auditing XML, XPath, and SQL Injection Vulnerabilities
Security Slicing for Auditing XML, XPath, and SQL Injection VulnerabilitiesSecurity Slicing for Auditing XML, XPath, and SQL Injection Vulnerabilities
Security Slicing for Auditing XML, XPath, and SQL Injection Vulnerabilities
 
Take Data Validation Seriously - Paul Milham, WildWorks
Take Data Validation Seriously - Paul Milham, WildWorksTake Data Validation Seriously - Paul Milham, WildWorks
Take Data Validation Seriously - Paul Milham, WildWorks
 
JSON Schema: Your API's Secret Weapon
JSON Schema: Your API's Secret WeaponJSON Schema: Your API's Secret Weapon
JSON Schema: Your API's Secret Weapon
 
Take Data Validation Seriously - Paul Milham, WildWorks
Take Data Validation Seriously - Paul Milham, WildWorksTake Data Validation Seriously - Paul Milham, WildWorks
Take Data Validation Seriously - Paul Milham, WildWorks
 
MongoDB World 2019: Life In Stitch-es
MongoDB World 2019: Life In Stitch-esMongoDB World 2019: Life In Stitch-es
MongoDB World 2019: Life In Stitch-es
 
To Err Is Human
To Err Is HumanTo Err Is Human
To Err Is Human
 
Third Party Auth in WebObjects
Third Party Auth in WebObjectsThird Party Auth in WebObjects
Third Party Auth in WebObjects
 
Open Source Search: An Analysis
Open Source Search: An AnalysisOpen Source Search: An Analysis
Open Source Search: An Analysis
 
Dependency injection in Scala
Dependency injection in ScalaDependency injection in Scala
Dependency injection in Scala
 
JWT - To authentication and beyond!
JWT - To authentication and beyond!JWT - To authentication and beyond!
JWT - To authentication and beyond!
 
I Don't Care About Security (And Neither Should You)
I Don't Care About Security (And Neither Should You)I Don't Care About Security (And Neither Should You)
I Don't Care About Security (And Neither Should You)
 
Html basics 11 form validation
Html basics 11 form validationHtml basics 11 form validation
Html basics 11 form validation
 
GraphQL & Relay - 串起前後端世界的橋樑
GraphQL & Relay - 串起前後端世界的橋樑GraphQL & Relay - 串起前後端世界的橋樑
GraphQL & Relay - 串起前後端世界的橋樑
 
Web Security - Hands-on
Web Security - Hands-onWeb Security - Hands-on
Web Security - Hands-on
 

Más de Donny Wals

Marketing strategie Arto
Marketing strategie ArtoMarketing strategie Arto
Marketing strategie Arto
Donny Wals
 

Más de Donny Wals (14)

Your 🧠 on Swift Concurrency
Your 🧠 on Swift ConcurrencyYour 🧠 on Swift Concurrency
Your 🧠 on Swift Concurrency
 
Using Combine, SwiftUI and callAsFunction to build an experimental localizati...
Using Combine, SwiftUI and callAsFunction to build an experimental localizati...Using Combine, SwiftUI and callAsFunction to build an experimental localizati...
Using Combine, SwiftUI and callAsFunction to build an experimental localizati...
 
The combine triad
The combine triadThe combine triad
The combine triad
 
Building reusable components with generics and protocols
Building reusable components with generics and protocolsBuilding reusable components with generics and protocols
Building reusable components with generics and protocols
 
Adopting tdd in the workplace
Adopting tdd in the workplaceAdopting tdd in the workplace
Adopting tdd in the workplace
 
Me and my importers
Me and my importersMe and my importers
Me and my importers
 
Adopting tdd in the workplace
Adopting tdd in the workplaceAdopting tdd in the workplace
Adopting tdd in the workplace
 
In Defense Of Core Data
In Defense Of Core DataIn Defense Of Core Data
In Defense Of Core Data
 
Effectively Producing And Shipping Frameworks For Multiple Platforms
Effectively Producing And Shipping Frameworks For Multiple PlatformsEffectively Producing And Shipping Frameworks For Multiple Platforms
Effectively Producing And Shipping Frameworks For Multiple Platforms
 
Improving apps with iOS 10 notifications (do iOS 2016)
Improving apps with iOS 10 notifications (do iOS 2016)Improving apps with iOS 10 notifications (do iOS 2016)
Improving apps with iOS 10 notifications (do iOS 2016)
 
Talk - git task managers and ci
Talk - git task managers and ciTalk - git task managers and ci
Talk - git task managers and ci
 
Developing in the Fastlane -> How LookLive uses Fastlane to automate and spee...
Developing in the Fastlane -> How LookLive uses Fastlane to automate and spee...Developing in the Fastlane -> How LookLive uses Fastlane to automate and spee...
Developing in the Fastlane -> How LookLive uses Fastlane to automate and spee...
 
Marketing strategie Arto
Marketing strategie ArtoMarketing strategie Arto
Marketing strategie Arto
 
Hoorcollege Flash 9-2-2012
Hoorcollege Flash 9-2-2012Hoorcollege Flash 9-2-2012
Hoorcollege Flash 9-2-2012
 

Último

CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICECHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
9953056974 Low Rate Call Girls In Saket, Delhi NCR
 
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdfintroduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
VishalKumarJha10
 

Último (20)

call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
 
Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
Direct Style Effect Systems -The Print[A] Example- A Comprehension AidDirect Style Effect Systems -The Print[A] Example- A Comprehension Aid
Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
 
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
 
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
 
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfLearn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
 
%in ivory park+277-882-255-28 abortion pills for sale in ivory park
%in ivory park+277-882-255-28 abortion pills for sale in ivory park %in ivory park+277-882-255-28 abortion pills for sale in ivory park
%in ivory park+277-882-255-28 abortion pills for sale in ivory park
 
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
 
%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain
%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain
%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain
 
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICECHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
 
MarTech Trend 2024 Book : Marketing Technology Trends (2024 Edition) How Data...
MarTech Trend 2024 Book : Marketing Technology Trends (2024 Edition) How Data...MarTech Trend 2024 Book : Marketing Technology Trends (2024 Edition) How Data...
MarTech Trend 2024 Book : Marketing Technology Trends (2024 Edition) How Data...
 
Sector 18, Noida Call girls :8448380779 Model Escorts | 100% verified
Sector 18, Noida Call girls :8448380779 Model Escorts | 100% verifiedSector 18, Noida Call girls :8448380779 Model Escorts | 100% verified
Sector 18, Noida Call girls :8448380779 Model Escorts | 100% verified
 
%in Midrand+277-882-255-28 abortion pills for sale in midrand
%in Midrand+277-882-255-28 abortion pills for sale in midrand%in Midrand+277-882-255-28 abortion pills for sale in midrand
%in Midrand+277-882-255-28 abortion pills for sale in midrand
 
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdfintroduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
 
Payment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdf
Payment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdfPayment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdf
Payment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdf
 
Chinsurah Escorts ☎️8617697112 Starting From 5K to 15K High Profile Escorts ...
Chinsurah Escorts ☎️8617697112  Starting From 5K to 15K High Profile Escorts ...Chinsurah Escorts ☎️8617697112  Starting From 5K to 15K High Profile Escorts ...
Chinsurah Escorts ☎️8617697112 Starting From 5K to 15K High Profile Escorts ...
 
AI & Machine Learning Presentation Template
AI & Machine Learning Presentation TemplateAI & Machine Learning Presentation Template
AI & Machine Learning Presentation Template
 
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
 
The Guide to Integrating Generative AI into Unified Continuous Testing Platfo...
The Guide to Integrating Generative AI into Unified Continuous Testing Platfo...The Guide to Integrating Generative AI into Unified Continuous Testing Platfo...
The Guide to Integrating Generative AI into Unified Continuous Testing Platfo...
 
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfThe Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
 
Azure_Native_Qumulo_High_Performance_Compute_Benchmarks.pdf
Azure_Native_Qumulo_High_Performance_Compute_Benchmarks.pdfAzure_Native_Qumulo_High_Performance_Compute_Benchmarks.pdf
Azure_Native_Qumulo_High_Performance_Compute_Benchmarks.pdf
 

The Testing Games: Mocking, yay!

  • 1. !
  • 2. The Testing Games: Mocking, yay! And may the mocks be ever in your favour
  • 3. I like testing It allows me to prove my code works
  • 4. But I’m also quite lazy And I know I’m not alone
  • 6. Improving our own tests 1. Create a set of guidelines
  • 7. Improving our own tests 1. Create a set of guidelines 2. Update the testing framework’s DSL (we use Quick)
  • 8. Improving our own tests 1. Create a set of guidelines 2. Update the testing framework’s DSL (we use Quick) 3. Change the way we think about mocking
  • 9. class AuthenticationServiceTests: QuickSpec { override func spec() { describe("The Service") { it("Should log in") { } } } }
  • 10. class AuthenticationServiceTests: QuickSpec { override func spec() { describe("The Service") { it("Should log in") { } } } } This isn’t too bad, right?
  • 11. class AuthenticationServiceTests: QuickSpec { override func spec() { describe("The Service") { it("Should log in") { let service = AuthenticationService(api: self) service.loginUser(withUsername: "Donny", password: "password") { user, error in expect(user?.name) == "Donny" expect(user?.id) == 1 } } } } } extension AuthenticationServiceTests: AuthenticationAPi { func loginUser(withUsername username: String, password: String, callback: (Data?, Error?) -> Void) { let json = """ {"status": "ok", "user": {"id": 1, "name": “Donny"}} """.data(using: .utf8) callback(json, nil) } }
  • 12. This is hard to read! class AuthenticationServiceTests: QuickSpec { override func spec() { describe("The Service") { it("Should log in") { let service = AuthenticationService(api: self) service.loginUser(withUsername: "Donny", password: "password") { user, error in expect(user?.name) == "Donny" expect(user?.id) == 1 } } } } } extension AuthenticationServiceTests: AuthenticationAPi { func loginUser(withUsername username: String, password: String, callback: (Data?, Error?) -> Void) { let json = """ {"status": "ok", "user": {"id": 1, "name": “Donny"}} """.data(using: .utf8) callback(json, nil) } }
  • 13. All values are hardcoded class AuthenticationServiceTests: QuickSpec { override func spec() { describe("The Service") { it("Should log in") { let service = AuthenticationService(api: self) service.loginUser(withUsername: "Donny", password: "password") { user, error in expect(user?.name) == "Donny" expect(user?.id) == 1 } } } } } extension AuthenticationServiceTests: AuthenticationAPi { func loginUser(withUsername username: String, password: String, callback: (Data?, Error?) -> Void) { let json = """ {"status": "ok", "user": {"id": 1, "name": “Donny"}} """.data(using: .utf8) callback(json, nil) } }
  • 14. The json is inline class AuthenticationServiceTests: QuickSpec { override func spec() { describe("The Service") { it("Should log in") { let service = AuthenticationService(api: self) service.loginUser(withUsername: "Donny", password: "password") { user, error in expect(user?.name) == "Donny" expect(user?.id) == 1 } } } } } extension AuthenticationServiceTests: AuthenticationAPi { func loginUser(withUsername username: String, password: String, callback: (Data?, Error?) -> Void) { let json = """ {"status": "ok", "user": {"id": 1, "name": “Donny"}} """.data(using: .utf8) callback(json, nil) } }
  • 15.
  • 16. let passwordError = """
 {"status": "error", "code": 1001, "message": "Wrong password"}
 """
  • 17. let passwordError = """
 {"status": "error", "code": 1001, "message": "Wrong password"}
 """
  • 18. let passwordError = """
 {"status": "error", "code": 1001, "message": "Wrong password"}
 """ let usernameError = """
 {"status": "error", "code": 1002, "message": "User not found"}
 """
  • 19. let passwordError = """
 {"status": "error", "code": 1001, "message": "Wrong password"}
 """ let usernameError = """
 {"status": "error", "code": 1002, "message": "User not found"}
 """
  • 20. let passwordError = """
 {"status": "error", "code": 1001, "message": "Wrong password"}
 """ let usernameError = """
 {"status": "error", "code": 1002, "message": "User not found"}
 """ let validationError = """
 {"status": "error", "code": 1003, "message": "Username or password missing"}
 """
  • 21. let passwordError = """
 {"status": "error", "code": 1001, "message": "Wrong password"}
 """ let usernameError = """
 {"status": "error", "code": 1002, "message": "User not found"}
 """ let validationError = """
 {"status": "error", "code": 1003, "message": "Username or password missing"}
 """
  • 22. let passwordError = """
 {"status": "error", "code": 1001, "message": "Wrong password"}
 """ let usernameError = """
 {"status": "error", "code": 1002, "message": "User not found"}
 """ let validationError = """
 {"status": "error", "code": 1003, "message": "Username or password missing"}
 """ let serverError = """
 {"status": "error", "code": 1004, "message": "Something went wrong on the server"}
 """
  • 23.
  • 24. let passwordError = """
 {"status": "error", "code": 1001, "message": "Wrong password"}
 """
  • 25. let passwordError = """
 {"status": "error", "code": 1001, "message": "Wrong password"}
 """
  • 26. let passwordError = """
 {"status": "error", "code": 1001, "message": "Wrong password"}
 """ let usernameError = """
 {"status": "error", "code": 1002, "message": "User not found"}
 """
  • 27. let passwordError = """
 {"status": "error", "code": 1001, "message": "Wrong password"}
 """ let usernameError = """
 {"status": "error", "code": 1002, "message": "User not found"}
 """
  • 28. let passwordError = """
 {"status": "error", "code": 1001, "message": "Wrong password"}
 """ let usernameError = """
 {"status": "error", "code": 1002, "message": "User not found"}
 """ let validationError = """
 {"status": "error", "code": 1003, "message": "Username or password missing"}
 """
  • 29. let passwordError = """
 {"status": "error", "code": 1001, "message": "Wrong password"}
 """ let usernameError = """
 {"status": "error", "code": 1002, "message": "User not found"}
 """ let validationError = """
 {"status": "error", "code": 1003, "message": "Username or password missing"}
 """
  • 30. let passwordError = """
 {"status": "error", "code": 1001, "message": "Wrong password"}
 """ let usernameError = """
 {"status": "error", "code": 1002, "message": "User not found"}
 """ let validationError = """
 {"status": "error", "code": 1003, "message": "Username or password missing"}
 """ let serverError = """
 {"status": "error", "code": 1004, "message": "Something went wrong on the server"}
 """
  • 31. let json = """ { "_id": "5b05709156da3c5e1a933dce", "index": 0, "guid": "ca6507f6-f462-478e-ad4d-f9e46f9ee88a", "isActive": true, "balance": "$3,125.88", "picture": "http://placehold.it/32x32", "age": 38, "eyeColor": "green", "name": { "first": "Dunlap", "last": "Lott" }, "company": "FUELTON", "email": "dunlap.lott@fuelton.biz", "phone": "+1 (888) 407-2414", "address": "883 Nixon Court, Wolcott, Marshall Islands, 2469", "about": "Dolor tempor ullamco deserunt tempor sunt esse irure ut fugiat. Irure occaecat fugiat deserunt ad. Ipsum laborum excepteur excepteur dolore qui aliqua. Ad tempor culpa commodo laborum laboris ut amet elit est cupidatat consectetur nostrud consequat.", "registered": "Saturday, August 5, 2017 12:57 PM",
  • 32. let json = """ { "_id": "5b05709156da3c5e1a933dce", "index": 0, "guid": "ca6507f6-f462-478e-ad4d-f9e46f9ee88a", "isActive": true, "balance": "$3,125.88", "picture": "http://placehold.it/32x32", "age": 38, "eyeColor": "green", "name": { "first": "Dunlap", "last": "Lott" }, "company": "FUELTON", "email": "dunlap.lott@fuelton.biz", "phone": "+1 (888) 407-2414", "address": "883 Nixon Court, Wolcott, Marshall Islands, 2469", "about": "Dolor tempor ullamco deserunt tempor sunt esse irure ut fugiat. Irure occaecat fugiat deserunt ad. Ipsum laborum excepteur excepteur dolore qui aliqua. Ad tempor culpa commodo laborum laboris ut amet elit est cupidatat consectetur nostrud consequat.", "registered": "Saturday, August 5, 2017 12:57 PM", 😒
  • 33. Duh! You should use fixtures
  • 34. let passwordError = ErrorFixture(type: .password) let usernameError = ErrorFixture(type: .username) let validationError = ErrorFixture(type: .validation) let serverError = ErrorFixture(type: .server)
  • 35. extension AuthenticationServiceTests: AuthenticationAPI { func loginUser(withUsername username: String, password: String, callback: (Data?, Error?) -> Void) { let json = """ {"status": "ok", "user": {"id": 1, "name": "Donny"} """.data(using: .utf8) callback(json, nil) } }
  • 36. extension AuthenticationServiceTests: AuthenticationAPI { func loginUser(withUsername username: String, password: String, callback: (Data?, Error?) -> Void) { let fixture = AuthenticationFixture(type: .success) callback(fixture.data, nil) } }
  • 37. class AuthenticationServiceTests: QuickSpec { override func spec() { describe("The Service") { it("Should log in") { let service = AuthenticationService(api: self) service.loginUser(withUsername: "Donny", password: "password") { user, error in expect(user?.name) == "Donny" expect(user?.id) == 1 } } } } } We just made the hardcoded values worse…
  • 38. Swift 4.0, to the rescue
  • 39. Swift 4.0, to the rescue {JSON} Decodable User
  • 40. Swift 4.0, to the rescue {JSON} Decodable User Why not do this?
  • 41. Swift 4.0, to the rescue {JSON} Decodable User Why not do this? {JSON} Encodable User
  • 42. describe("The Authentication Service") { it("Should log in a user") { let mockUser = User(name: "Donny", id: 1) let userData = try! JSONEncoder().encode(mockUser) let authenticationApi = MockAuthenticationApi(responseData: userData, error: nil) let service = AuthenticationService(api: authenticationApi) service.loginUser(withUsername: "Donny", password: "password") { user, error in expect(user?.name) == mockUser.name expect(user?.id) == mockUser.id } } } Now we’re getting somewhere!
  • 43. struct Prize: Codable { enum PrizeType: Int, Codable { case regular = 0, main = 1 } let id: Int let type: PrizeType let label: String } struct Play: Codable { let id: Int let acquiredPrizes: [Prize] } struct Game: Codable { let id: Int let availablePrizes: [Prize] let name: String let closeDate: Date let plays: [Play] let maxPlayCount: Int } struct PlayResponse { let game: Game let currentPlay: Play } But we’re not quite there
  • 45. Mockable protocol Mockable: Codable { associatedtype BaseType: Decodable } extension Mockable { var data: Data? { return try? JSONEncoder().encode(self) } var baseType: BaseType? { guard let data = self.data else { return nil } return try? JSONDecoder().decode(BaseType.self, from: data) } }
  • 46. Mockable protocol Mockable: Codable { associatedtype BaseType: Decodable } extension Mockable { var data: Data? { return try? JSONEncoder().encode(self) } var baseType: BaseType? { guard let data = self.data else { return nil } return try? JSONDecoder().decode(BaseType.self, from: data) } }
  • 47. Mockable protocol Mockable: Codable { associatedtype BaseType: Decodable } extension Mockable { var data: Data? { return try? JSONEncoder().encode(self) } var baseType: BaseType? { guard let data = self.data else { return nil } return try? JSONDecoder().decode(BaseType.self, from: data) } }
  • 48. Mock all the things! class MockPrize: Mockable { typealias BaseType = Prize enum PrizeType: Int, Codable { case regular = 0, main = 1 } var id = 0 var type = PrizeType.regular var label = "Default Label" } class MockPlay: Mockable { typealias BaseType = Play var id = 0 var acquiredPrizes = [MockPrize]() } class MockGame: Mockable { typealias BaseType = Game var id = 0 var availablePrizes = [MockPrize]() var name = "Default game name" var closeDate = Date() var plays = [MockPlay]() var maxPlayCount = 1 } class MockPlayResponse: Mockable { typealias BaseType = PlayResponse var game = MockGame() var currentPlay = MockPlay() }
  • 49. Mock all the things! I know… it would be great if you could skip this! class MockPrize: Mockable { typealias BaseType = Prize enum PrizeType: Int, Codable { case regular = 0, main = 1 } var id = 0 var type = PrizeType.regular var label = "Default Label" } class MockPlay: Mockable { typealias BaseType = Play var id = 0 var acquiredPrizes = [MockPrize]() } class MockGame: Mockable { typealias BaseType = Game var id = 0 var availablePrizes = [MockPrize]() var name = "Default game name" var closeDate = Date() var plays = [MockPlay]() var maxPlayCount = 1 } class MockPlayResponse: Mockable { typealias BaseType = PlayResponse var game = MockGame() var currentPlay = MockPlay() }
  • 50. What does this give us?
  • 51. What does this give us? • Mutable “clones” of the models
  • 52. What does this give us? • Mutable “clones” of the models • (Sensible) Default values
  • 53. What does this give us? • Mutable “clones” of the models • (Sensible) Default values • Objects that are ready to be encoded to JSON
  • 54. What does this give us? • Mutable “clones” of the models • (Sensible) Default values • Objects that are ready to be encoded to JSON • Some spare time because we don’t need to collect mocks for each configuration
  • 55. What does this give us? • Mutable “clones” of the models • (Sensible) Default values • Objects that are ready to be encoded to JSON • Some spare time because we don’t need to collect mocks for each configuration • Typo-free comparisons
  • 56. Now this is MotherMocking nice! describe("When playing a game and the server returns no prizes") { let gameData = MockGame().data let gameApi = MockGameApi(preparedData: gameData, error: nil) let gameService = GameService(api: gameApi) it("The parsed model should not contain prizes") { gameService.play { game, error in expect(game?.plays.first?.acquiredPrizes.count) == 0 } } }
  • 57. Now this is MotherMocking nice! describe("When playing a game and the server returns a main prize") { let game = MockGame() let play = MockPlay() let prize = MockPrize() prize.type = .main play.acquiredPrizes = [prize] game.plays = [play] let gameApi = MockGameApi(preparedData: game.data, error: nil) let gameService = GameService(api: gameApi) it("The parsed model should contain a single main prize") { gameService.play { game, error in expect(game?.plays.first?.acquiredPrizes.count) == 1 expect(game?.plays.first?.acquiredPrizes.first?.type) == .main } } }
  • 58. Easier configuration of mocks encourages testing more cases
  • 59. What is not easier with this approach?
  • 60. What is not easier with this approach? • It’s hard to test what your code does if you get malformed json
  • 61. What is not easier with this approach? • It’s hard to test what your code does if you get malformed json • You can’t purposefully omit certain properties (unless you make a special mock class for that case)
  • 62. What is not easier with this approach? • It’s hard to test what your code does if you get malformed json • You can’t purposefully omit certain properties (unless you make a special mock class for that case) • You still need a single source of truth for what data goes into a model. You could coordinate some sort of setup with the back-end team so you can fetch a blueprint for each model
  • 64. Some final notes • It’s easier to mock responses if your api services return Data instead of models
  • 65. Some final notes • It’s easier to mock responses if your api services return Data instead of models • Make smaller services so you can easily create focussed mocks for them
  • 66. Some final notes • It’s easier to mock responses if your api services return Data instead of models • Make smaller services so you can easily create focussed mocks for them • Your tests shouldn’t rely on a network connection
  • 67. Some final notes • It’s easier to mock responses if your api services return Data instead of models • Make smaller services so you can easily create focussed mocks for them • Your tests shouldn’t rely on a network connection • This (obviously) is not a silver bullet
  • 68. Some final notes • It’s easier to mock responses if your api services return Data instead of models • Make smaller services so you can easily create focussed mocks for them • Your tests shouldn’t rely on a network connection • This (obviously) is not a silver bullet • Large or complex configurations can still be fed to Mockable objects since they conform to Codable
  • 69. #