Slide 1

Slide 1 text

Wooseong Kim, StyleShare Async network logic testing using Protocol Session 2 #swiftkorea Testing

Slide 2

Slide 2 text

Problems in networking logic testing Concept - Test Stub Case study (with Test Stub)

Slide 3

Slide 3 text

Problems in networking logic testing

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

Want to test here

Slide 6

Slide 6 text

Want to test here But it’s hard

Slide 7

Slide 7 text

Because of this!

Slide 8

Slide 8 text

What if… Offline? Network problem? Server issue? Auth issues?

Slide 9

Slide 9 text

What if… Offline? Network problem? Server issue? Auth issues?

Slide 10

Slide 10 text

What if… Offline? Network problem? Server issue? Auth issues?

Slide 11

Slide 11 text

What if… Offline? Network problem? Server issue? Auth issues?

Slide 12

Slide 12 text

Offline? Network problem? Server issue? Auth issues? What if…

Slide 13

Slide 13 text

If the network fails, tests will fail too. What if…

Slide 14

Slide 14 text

If the network fails, tests will fail too. What if… Unhappy

Slide 15

Slide 15 text

Expected Real world →

Slide 16

Slide 16 text

Expected Solution →

Slide 17

Slide 17 text

Fake networking?

Slide 18

Slide 18 text

Fake Networking Request Response Request Response Same response for same request.

Slide 19

Slide 19 text

Fake Networking Useful in test environment Production Testing

Slide 20

Slide 20 text

Called Test Stub Production Testing

Slide 21

Slide 21 text

So today’s real topic is,

Slide 22

Slide 22 text

Wooseong Kim, StyleShare #swiftkorea Testing Async network logic testing using Test Stub Session 2

Slide 23

Slide 23 text

Case study (with Test Stub)

Slide 24

Slide 24 text

Case study - Log In

Slide 25

Slide 25 text

No content

Slide 26

Slide 26 text

No content

Slide 27

Slide 27 text

No content

Slide 28

Slide 28 text

Log-In Button Tap ID/PW Response HTTP Request HTTP Response

Slide 29

Slide 29 text

Test Stub → ID/PW Response Testing environments Log-In Button Tap

Slide 30

Slide 30 text

Structures

Slide 31

Slide 31 text

Production vs Test

Slide 32

Slide 32 text

Production vs Test Different operation, but same interface

Slide 33

Slide 33 text

Production vs Test …so they can conform the same protocol!

Slide 34

Slide 34 text

Production vs Test Difference service on different environment

Slide 35

Slide 35 text

Codes

Slide 36

Slide 36 text

protocol AuthServiceType { func logIn(id: String, pw: String, ...) }

Slide 37

Slide 37 text

protocol AuthServiceType { func logIn(id: String, pw: String, ...) } // Production class AuthService: AuthServiceType { func logIn(id: String, pw: String, ...) { // Networking logic } }

Slide 38

Slide 38 text

protocol AuthServiceType { func logIn(id: String, pw: String, ...) } // Production class AuthService: AuthServiceType { func logIn(id: String, pw: String, ...) { // Networking logic } } // Test class StubAuthService: AuthServiceType { func logIn(id: String, pw: String, ...) { // Stub logic } }

Slide 39

Slide 39 text

// Production class LogInViewController: UIViewController { var authService: AuthServiceType! ... } let viewController = LogInViewController() viewController.authService = AuthService() // Test let viewController = LogInViewController() viewController.authService = StubAuthService()

Slide 40

Slide 40 text

Writing test code with StubAuthService

Slide 41

Slide 41 text

// Should store the token after logged in class LogInViewController: UIViewController { ... }

Slide 42

Slide 42 text

// Should store the token after logged in class LogInViewController: UIViewController { var authService: AuthServiceType! func logInButtonDidTap() { authService.logIn(id: "id", pw: "pw", completion: { response in // Completion logic } } }

Slide 43

Slide 43 text

protocol AuthServiceType { func logIn(id: String, pw: String, completion: (Response) -> Void) } // Production class AuthService: AuthServiceType { func logIn(id: String, pw: String, completion: (Response) -> Void) { // Networking logic } } // Test class StubAuthService: AuthServiceType { func logIn(id: String, pw: String, completion: (Response) -> Void) { // Stub logic } }

Slide 44

Slide 44 text

// Test class StubAuthService: AuthServiceType { func logIn(id: String, pw: String, completion: (Response) -> Void) { let response = Response(token: "token") // Works synchronously completion(response) } }

Slide 45

Slide 45 text

// Test class StubAuthService: AuthServiceType { func logIn(id: String, pw: String, completion: (Response) -> Void) { let response = Response(token: "token") completion(response) } } // Production class LogInViewController: UIViewController { func logInButtonDidTap() { authService.logIn(id: "id", pw: "pw", completion: { response in // Passed to here! } } }

Slide 46

Slide 46 text

func testLogIn() { ...? }

Slide 47

Slide 47 text

func testLogIn() { // given let viewController = LogInViewController() viewController.authService = StubAuthService() }

Slide 48

Slide 48 text

func testLogIn() { // given let viewController = LogInViewController() viewController.authService = StubAuthService() // when viewController.logInButtonDidTap() }

Slide 49

Slide 49 text

func testLogIn() { // given let viewController = LogInViewController() viewController.authService = StubAuthService() // when viewController.logInButtonDidTap() // then XCTAssertEqual(viewController.token, "token") }

Slide 50

Slide 50 text

func testLogIn() { // given let viewController = LogInViewController() viewController.authService = StubAuthService() // when viewController.logInButtonDidTap() // then XCTAssertEqual(viewController.token, "token") // Failed }

Slide 51

Slide 51 text

// Production class LogInViewController: UIViewController { var authService: AuthServiceType! var token: String func logInButtonDidTap() { authService.logIn(id: "id", pw: "pw", completion: { response in self.token = response.token } } }

Slide 52

Slide 52 text

// Production class LogInViewController: UIViewController { var authService: AuthServiceType! var token: String func logInButtonDidTap() { authService.logIn(id: "id", pw: "pw", completion: { response in self.token = response.token } } } // Test func testLogIn() { ... XCTAssertEqual(viewController.token, "token") // Succeeded }

Slide 53

Slide 53 text

Various cases of response - Success or Failure (Wrong password)

Slide 54

Slide 54 text

class StubAuthService: AuthServiceType { func logIn(id: String, pw: String, completion: (Response) -> Void) { let response = Response(token: "token") // Success case completion(response) } }

Slide 55

Slide 55 text

class StubAuthService: AuthServiceType { func logIn(id: String, pw: String, completion: (Response) -> Void) { let response = Response(token: "token") // Success case completion(response) } } func testLogIn_success() { ... XCTAssertEqual(viewController.token, "token") } func testLogIn_wrongPassword() { ...? }

Slide 56

Slide 56 text

class StubAuthService: AuthServiceType { var stubResponse: Response! func logIn(id: String, pw: String, completion: (Response) -> Void) { completion(stubResponse) } }

Slide 57

Slide 57 text

class StubAuthService: AuthServiceType { var stubResponse: Response! func logIn(id: String, pw: String, completion: (Response) -> Void) { completion(stubResponse) } } func testLogIn_success() { let authService = StubAuthService() authService.stubResponse = Response(token: "token") ... viewController.logInButtonDidTap() XCTAssertEqual(viewController.token, "token") }

Slide 58

Slide 58 text

class StubAuthService: AuthServiceType { var stubResponse: Response! func logIn(id: String, pw: String, completion: (Response) -> Void) { completion(stubResponse) } } func testLogIn_success() { ... } func testLogIn_wrongPassword() { let authService = StubAuthService() authService.stubResponse = Response(error: LogInError.wrongPassword) ... viewController.logInButtonDidTap() XCTAssertEqual(viewController.logInError, LogInError.wrongPassword) }

Slide 59

Slide 59 text

class StubAuthService: AuthServiceType { var stubResponse: Response! func logIn(id: String, pw: String, completion: (Response) -> Void) { completion(stubResponse) } } func testLogIn_success() { ... } func testLogIn_wrongPassword() { let authService = StubAuthService() authService.stubResponse = Response(error: LogInError.wrongPassword) ... viewController.logInButtonDidTap() XCTAssertEqual(viewController.logInError, LogInError.wrongPassword) }

Slide 60

Slide 60 text

enum LogInError { case wrongPassword case idNotExist } class LogInViewController: UIViewController { var authService: AuthServiceType! var token: String var logInError: LogInError func logInButtonDidTap() { authService.logIn(id: "id", pw: "pw", completion: { response in if response.status == .success { self.token = response.token } else if response.status == .failure { self.logInError = response.error } } } }

Slide 61

Slide 61 text

enum LogInError { case wrongPassword case idNotExist } class LogInViewController: UIViewController { var authService: AuthServiceType! var token: String var logInError: LogInError func logInButtonDidTap() { authService.logIn(id: "id", pw: "pw", completion: { response in if response.status == .success { self.token = response.token } else if response.status == .failure { self.logInError = response.error } } } } func testLogIn_wrongPassword() { ... // Succeeded XCTAssertEqual(viewController.logInError, LogInError.wrongPassword) }

Slide 62

Slide 62 text

Stubber

Slide 63

Slide 63 text

Stubber created by @devxoul Stubber

Slide 64

Slide 64 text

Stubber is a minimal method stub for Swift.

Slide 65

Slide 65 text

Stubber is a minimal method stub for Swift. https://github.com/devxoul/Stubber

Slide 66

Slide 66 text

Summary Tests may fail because of network dependency …so let’s write test codes with Test Stub May the Test Stub be with you.

Slide 67

Slide 67 text

Thank you! Wooseong Kim, StyleShare [email protected] #swiftkorea