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
2
410
Async network logic testing using Protocol (Test Stub)
presented at Swift Korea meetup (2017/12/13)
Wooseong Kim
December 13, 2017
Tweet
Share
More Decks by Wooseong Kim
See All by Wooseong Kim
Tuist, 도입은 했는데 그래서 Modular Architecture 는 어떻게 만들어 가나요?
innocarpe
0
580
Modular Architecture w/ Tuist
innocarpe
0
720
Test-Driven-Development with ReactorKit in StyleShare
innocarpe
1
970
Developing iOS with Rx, MVVM
innocarpe
1
180
git + Pull Request + Code Review and Project Management with Agile
innocarpe
0
93
Online Board(Trello), Scrum(Pivotal Tracker), Kanban(JIRA Agile)
innocarpe
0
130
Android TV at Google Developers Summit
innocarpe
0
100
경북대도서관 스마트앱 최종발표
innocarpe
0
200
Other Decks in Programming
See All in Programming
デザイナーが Androidエンジニアに 挑戦してみた
874wokiite
0
280
複雑なドメインに挑む.pdf
yukisakai1225
5
1.1k
奥深くて厄介な「改行」と仲良くなる20分
oguemon
1
510
Rancher と Terraform
fufuhu
2
240
個人軟體時代
ethanhuang13
0
320
rage against annotate_predecessor
junk0612
0
160
2025 年のコーディングエージェントの現在地とエンジニアの仕事の変化について
azukiazusa1
22
11k
JSONataを使ってみよう Step Functionsが楽しくなる実践テクニック #devio2025
dafujii
1
510
Tool Catalog Agent for Bedrock AgentCore Gateway
licux
6
2.3k
テストカバレッジ100%を10年続けて得られた学びと品質
mottyzzz
2
560
アセットのコンパイルについて
ojun9
0
120
為你自己學 Python - 冷知識篇
eddie
1
350
Featured
See All Featured
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
126
53k
How to Think Like a Performance Engineer
csswizardry
26
1.9k
Context Engineering - Making Every Token Count
addyosmani
1
27
Statistics for Hackers
jakevdp
799
220k
Producing Creativity
orderedlist
PRO
347
40k
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
31
2.2k
Docker and Python
trallard
45
3.6k
Product Roadmaps are Hard
iamctodd
PRO
54
11k
CSS Pre-Processors: Stylus, Less & Sass
bermonpainter
358
30k
The Straight Up "How To Draw Better" Workshop
denniskardys
236
140k
Git: the NoSQL Database
bkeepers
PRO
431
66k
Stop Working from a Prison Cell
hatefulcrawdad
271
21k
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