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
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()
}
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
}
}
}
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