Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Async network logic testing using Protocol (Tes...
Search
Wooseong Kim
December 13, 2017
Programming
460
2
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Async network logic testing using Protocol (Test Stub)
presented at Swift Korea meetup (2017/12/13)
Wooseong Kim
December 13, 2017
More Decks by Wooseong Kim
See All by Wooseong Kim
Tuist, 도입은 했는데 그래서 Modular Architecture 는 어떻게 만들어 가나요?
innocarpe
0
680
Modular Architecture w/ Tuist
innocarpe
0
790
Test-Driven-Development with ReactorKit in StyleShare
innocarpe
1
1k
Developing iOS with Rx, MVVM
innocarpe
1
220
git + Pull Request + Code Review and Project Management with Agile
innocarpe
0
110
Online Board(Trello), Scrum(Pivotal Tracker), Kanban(JIRA Agile)
innocarpe
0
150
Android TV at Google Developers Summit
innocarpe
0
130
경북대도서관 스마트앱 최종발표
innocarpe
0
220
Other Decks in Programming
See All in Programming
dRuby over BLE
makicamel
2
340
Oxlintのカスタムルールの現況
syumai
6
1.1k
The ROI of Quarkus for Spring Boot Applications
hollycummins
0
120
Spring Security 実践 ─ GraphQL APIで実務に役立つ 認証・認可 を学ぶ
wagyu
0
230
DynamoDBには集計系のクエリがないけどなんとかしたい
musan
1
140
[2026年度第1回ORセミナー] 計画最適化ベンチャーと競技プログラミング人材
terryu16
0
260
Skillsは効率化、Agentsは"自分の拡張"——Builder時代のエージェント編成(CC Night 2026)
wemra
1
130
気づいたらRubyで100作品 ー クリエイティブコーディングが生活の一部になるまで / 100 Ruby Sketches Later: How Creative Coding Became Part of My Life
chobishiba
3
580
New "Type" system on PicoRuby
pocke
1
930
Javaの型とAI時代に型が大事な理由 / java types and type in AI era
kishida
2
140
Claspは野良GASの夢をみるか
takter00
0
190
さぁV100、メモリをお食べ・・・
nilpe
0
140
Featured
See All Featured
Code Review Best Practice
trishagee
74
20k
Navigating Team Friction
lara
192
16k
How to make the Groovebox
asonas
2
2.2k
Measuring & Analyzing Core Web Vitals
bluesmoon
9
870
Speed Design
sergeychernyshev
33
1.8k
Effective software design: The role of men in debugging patriarchy in IT @ Voxxed Days AMS
baasie
0
410
Leveraging LLMs for student feedback in introductory data science courses - posit::conf(2025)
minecr
1
280
RailsConf 2023
tenderlove
30
1.5k
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
12
1.2k
YesSQL, Process and Tooling at Scale
rocio
174
15k
技術選定の審美眼(2025年版) / Understanding the Spiral of Technologies 2025 edition
twada
PRO
118
120k
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
128
56k
Transcript
Wooseong Kim, StyleShare Async network logic testing using Protocol Session
2 #swiftkorea Testing
Problems in networking logic testing Concept - Test Stub Case
study (with Test Stub)
Problems in networking logic testing
None
Want to test here
Want to test here But it’s hard
Because of this!
What if… Offline? Network problem? Server issue? Auth issues?
What if… Offline? Network problem? Server issue? Auth issues?
What if… Offline? Network problem? Server issue? Auth issues?
What if… Offline? Network problem? Server issue? Auth issues?
Offline? Network problem? Server issue? Auth issues? What if…
If the network fails, tests will fail too. What if…
If the network fails, tests will fail too. What if…
Unhappy
Expected Real world →
Expected Solution →
Fake networking?
Fake Networking Request Response Request Response Same response for same
request.
Fake Networking Useful in test environment Production Testing
Called Test Stub Production Testing
So today’s real topic is,
Wooseong Kim, StyleShare #swiftkorea Testing Async network logic testing using
Test Stub Session 2
Case study (with Test Stub)
Case study - Log In
None
None
None
Log-In Button Tap ID/PW Response HTTP Request HTTP Response
Test Stub → ID/PW Response Testing environments Log-In Button Tap
Structures
Production vs Test
Production vs Test Different operation, but same interface
Production vs Test …so they can conform the same protocol!
Production vs Test Difference service on different environment
Codes
protocol AuthServiceType { func logIn(id: String, pw: String, ...) }
protocol AuthServiceType { func logIn(id: String, pw: String, ...) }
// Production class AuthService: AuthServiceType { func logIn(id: String, pw: String, ...) { // Networking logic } }
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 } }
// Production class LogInViewController: UIViewController { var authService: AuthServiceType! ...
} let viewController = LogInViewController() viewController.authService = AuthService() // Test let viewController = LogInViewController() viewController.authService = StubAuthService()
Writing test code with StubAuthService
// Should store the token after logged in class LogInViewController:
UIViewController { ... }
// 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 } } }
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 } }
// Test class StubAuthService: AuthServiceType { func logIn(id: String, pw:
String, completion: (Response) -> Void) { let response = Response(token: "token") // Works synchronously completion(response) } }
// 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! } } }
func testLogIn() { ...? }
func testLogIn() { // given let viewController = LogInViewController() viewController.authService
= StubAuthService() }
func testLogIn() { // given let viewController = LogInViewController() viewController.authService
= StubAuthService() // when viewController.logInButtonDidTap() }
func testLogIn() { // given let viewController = LogInViewController() viewController.authService
= StubAuthService() // when viewController.logInButtonDidTap() // then XCTAssertEqual(viewController.token, "token") }
func testLogIn() { // given let viewController = LogInViewController() viewController.authService
= StubAuthService() // when viewController.logInButtonDidTap() // then XCTAssertEqual(viewController.token, "token") // Failed }
// 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 } } }
// 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 }
Various cases of response - Success or Failure (Wrong password)
class StubAuthService: AuthServiceType { func logIn(id: String, pw: String, completion:
(Response) -> Void) { let response = Response(token: "token") // Success case completion(response) } }
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() { ...? }
class StubAuthService: AuthServiceType { var stubResponse: Response! func logIn(id: String,
pw: String, completion: (Response) -> Void) { completion(stubResponse) } }
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") }
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) }
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) }
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 } } } }
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) }
Stubber
Stubber created by @devxoul Stubber
Stubber is a minimal method stub for Swift.
Stubber is a minimal method stub for Swift. https://github.com/devxoul/Stubber
Summary Tests may fail because of network dependency …so let’s
write test codes with Test Stub May the Test Stub be with you.
Thank you! Wooseong Kim, StyleShare
[email protected]
#swiftkorea