Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

トークンリフレッシュ処理を含むAPIClientのテスト #hakata_test_night

634 views

Published on

2018/12/14に開催されたHAKATA Test Nightの登壇資料です。

Published in: Technology
  • Be the first to comment

トークンリフレッシュ処理を含むAPIClientのテスト #hakata_test_night

  1. 1. APIClient 2018/12/14 HAKATA Test Night #1 #hakata_test_night 
 (@ktanaka117)
  2. 2. • / @ktanaka117 • • : • R&D Swift TDD • PEAKS iOS
  3. 3. Booth iOS 
 💪
  4. 4. APIClient 2018/12/14 HAKATA Test Night #1 #hakata_test_night 
 (@ktanaka117)
  5. 5. • • • • • • • •
  6. 6. AccessToken/RefreshToken AccessToken
  7. 7. • AccessToken AuthError • Refresh→Retry AccessToken … • 5 • • • 1 1APICall API
  8. 8. • Auth Error retry 1 • 5
  9. 9. • • • • Etc…
  10. 10. Android 
 OkHttp
  11. 11. • • iOS • iOS 

  12. 12. • Interactor - APIClient SessionManager • Refresh Request • Refresh Request • APIKit→Alamofire • APIKit Refresh/Retry • Request-Completion 
 Alamofire
  13. 13. : RequestRetrier/RequestAdapter
  14. 14. : RequestRetrier/RequestAdapter
  15. 15. • APIClient • APIClient •
  16. 16. • 1 • 2 • 2 • HTTP Status
  17. 17. - 1 • • [200] • [400] • [401] refresh 

  18. 18. - 1 • • [200] • [400] • [401] • [401, 200(refresh), 200(retry)] • [401, 200(refresh), 400(retry)] • [401, 200(refresh), 401(retry)] Refresh/Retry 

  19. 19. - 2 • • [200(a), 200(b)] (ab ) • [400(a), 400(b)] (ab ) • [401(a), 401(b)]
  20. 20. - 2 • • [200(a), 200(b)] / [400(a), 400(b)] (ab ) • [401(a), 200(a, refresh), 200(a, retry), 200(b)] (ab ) • [401(a), 200(a, refresh), 400(a, retry), 200(b)] (ab ) • [401(a), 400(a, refresh), 401(b)] (ab ) • [401(a), 401(a, refresh), 401(b)] (ab )
  21. 21. - 2 •
  22. 22. - 2 • • [200(a), 200(b)] / [400(a), 400(b)] (ab ) • [400(a), 401(b)] / [200(a) / 400(b)] (ab , 1 ) • [401(a), 401(b), 200(a, refresh), 200(a, retry), 200(b, retry)] (b Refresh ) • [401(a), 200(a, refresh), 401(b), 200(a, retry), 200(b, retry)] (b Refresh ) • etc…
  23. 23.
  24. 24. • Stub • XCTContext • AccessToken, RefreshToken • AccessToken, RefreshToken
  25. 25. • Stub • XCTContext • AccessToken, RefreshToken • AccessToken, RefreshToken 🙇
  26. 26. Stub var responses = [authError, successWithAccessToken, success]
 stub(condition: isHost("example.com")) { request in return responses.removeFirst() } // ...... // ... OHHTTPStubs.removeAllStubs() responses test function …
  27. 27. Stub HTTPStub.activate( condition: isHost("example.com"), responses: [authError, successWithAccessToken, success]) // ...... // ... HTTPStub.deactivate() responses 

  28. 28. Stub struct HTTPStub { static func activate(condition: @escaping OHHTTPStubsTestBlock, responses: [OHHTTPStubsResponse]) { var responsesArray = responses stub(condition: condition) { request in let response = responsesArray.removeFirst() return response } } static func deactivate() { OHHTTPStubs.removeAllStubs() } }
  29. 29. Blog
  30. 30. XCTContext XCTContext.runActivity(named: case1) { [weak self] _ in HTTPStub.activate(responses: [authError, successWithAccesstoken, success]) expectation.expectedFulfillmentCount = 1
 sessionManager.requestData(TestRequest()) .subscribe(onSuccess: { data, _ in let success = try? JSONDecoder().decodeSuccess(from: data) XCTAssertEqual(success, OHHTTPStubsResponse.Success(data: "Success")) expectation.fulfill() }, onError: { error in XCTFail(error.localizedDescription) expectation.fulfill() }).disposed(by: disposeBag) self?.waitForExpectations(timeout: 1.0) clean() }
  31. 31. XCTContext XCTContext.runActivity(named: case1) { [weak self] _ in HTTPStub.activate(responses: [authError, successWithAccesstoken, success]) expectation.expectedFulfillmentCount = 1
 sessionManager.requestData(TestRequest()) .subscribe(onSuccess: { data, _ in let success = try? JSONDecoder().decodeSuccess(from: data) XCTAssertEqual(success, OHHTTPStubsResponse.Success(data: "Success")) expectation.fulfill() }, onError: { error in XCTFail(error.localizedDescription) expectation.fulfill() }).disposed(by: disposeBag) self?.waitForExpectations(timeout: 1.0) clean() } 👃
  32. 32. XCTContext XCTContext.runActivity(named: case1) { [weak self] _ in HTTPStub.activate(responses: [authError, successWithAccesstoken, success]) expectation.expectedFulfillmentCount = 1
 sessionManager.requestData(TestRequest()) .subscribe(onSuccess: { data, _ in let success = try? JSONDecoder().decodeSuccess(from: data) XCTAssertEqual(success, OHHTTPStubsResponse.Success(data: "Success")) expectation.fulfill() }, onError: { error in XCTFail(error.localizedDescription) expectation.fulfill() }).disposed(by: disposeBag) self?.waitForExpectations(timeout: 1.0) clean() } Stub 👀
  33. 33. XCTContext XCTContext.runActivity(named: case1) { [weak self] _ in HTTPStub.activate(responses: [authError, successWithAccesstoken, success]) expectation.expectedFulfillmentCount = 1
 sessionManager.requestData(TestRequest()) .subscribe(onSuccess: { data, _ in let success = try? JSONDecoder().decodeSuccess(from: data) XCTAssertEqual(success, OHHTTPStubsResponse.Success(data: "Success")) expectation.fulfill() }, onError: { error in XCTFail(error.localizedDescription) expectation.fulfill() }).disposed(by: disposeBag) self?.waitForExpectations(timeout: 1.0) clean() } expectation 👀
  34. 34. XCTContext XCTContext.runActivity(named: case1) { [weak self] _ in HTTPStub.activate(responses: [authError, successWithAccesstoken, success]) expectation.expectedFulfillmentCount = 1
 sessionManager.requestData(TestRequest()) .subscribe(onSuccess: { data, _ in let success = try? JSONDecoder().decodeSuccess(from: data) XCTAssertEqual(success, OHHTTPStubsResponse.Success(data: "Success")) expectation.fulfill() }, onError: { error in XCTFail(error.localizedDescription) expectation.fulfill() }).disposed(by: disposeBag) self?.waitForExpectations(timeout: 1.0) clean() } 👀
  35. 35. XCTContext XCTContext.runActivity(named: case1) { [weak self] _ in HTTPStub.activate(responses: [authError, successWithAccesstoken, success]) expectation.expectedFulfillmentCount = 1
 sessionManager.requestData(TestRequest()) .subscribe(onSuccess: { data, _ in let success = try? JSONDecoder().decodeSuccess(from: data) XCTAssertEqual(success, OHHTTPStubsResponse.Success(data: “Success")) expectation.fulfill() }, onError: { error in XCTFail(error.localizedDescription) expectation.fulfill() }).disposed(by: disposeBag) self?.waitForExpectations(timeout: 1.0) clean() } 👀
  36. 36. XCTContext extension XCTContext { static func runWaitActivity(testCase: XCTestCase, name name: String, timeout: TimeInterval = 1.0, prepare: (() -> Void)? = nil, then: (XCTestExpectation) -> Void, clean: (() -> Void)? = nil) { runActivity(named: named) { _ in let expectation = name prepare?() then(expectation) testCase.waitForExpectations(timeout: timeout, handler: nil) clean?() } } }
  37. 37. XCTContext XCTContext .runWaitActivity(testCase: self, named: case1, prepare: { HTTPStub.activate(responses: [authError, successWithAccesstoken, success]) }, then: { expectation in expectation.expectedFulfillmentCount = 1 sessionManager.requestData(TestRequest()) .subscribe(onSuccess: { data, _ in let success = try? JSONDecoder().decodeSuccess(from: data) XCTAssertEqual(success, OHHTTPStubsResponse.Success(data: "Success")) expectation.fulfill() }, onError: { error in XCTFail(error.localizedDescription) expectation.fulfill() }) .disposed(by: disposeBag) }, clean: clean) 
 😄
  38. 38. • • 
 •

×