티스토리 뷰

개인적으로 생각하는 Moya를 다룬 포스트입니다.

 

Moya가 등장한 계기는 Alamofire와 URLSession에 있습니다.

URLSession의 코드를 더 가독성 좋게 작성하기 위해 나온 라이브러리가 Alamofire입니다.

하지만 Alamofire도 테스트 코드를 작성하기 위해서는 URLProtocol을 사용해야 했습니다.

그렇기에 이를 테스트하기 좋게 하기 위해 Alamofire를 한번 더 추상화한 라이브러리가 Moya입니다.

 

이러한 내용들로 보아 Moya는 URLSession보다 코드 가독성이 좋고, URLProtocol보다 테스트하기 편하다는 장점이 있다 생각합니다.

 

Github에서 token을 가져오는 API 사용 로직을 구현해보겠습니다.

 

사용법

import Moya

enum GithubAPI {
    case exchangeToken(String)
}

extension GithubAPI: TargetType {
    var baseURL: URL {
        switch self {
        case .exchangeToken(_):
            return URL(string: "https://github.com")!
    }
    
    var path: String {
        switch self {
        case .exchangeToken(_):
            return "/login/oauth/access_token"
        }
    }
    
    var method: Method {
        switch self {
        case .exchangeToken(_):
            return .post
        }
    }
    
    var task: Task {
        switch self {
        case .exchangeToken(let code):
            let params: [String: Any] = [
                "client_id": "clientID",
                "client_secret": "clientSecret",
                "code": code
            ]
            return .requestParameters(parameters: params, encoding: URLEncoding.queryString)
    }
    
    var sampleData: Data {
        switch self {
        case .exchangeToken(_):
            guard let json = Bundle.main.path(forResource: "MockAccessToken", ofType: "json") else { return Data() }
            guard let jsonString = try? String(contentsOfFile: json) else { return Data() }
            guard let mockData = jsonString.data(using: .utf8) else { return Data() }
            return mockData
        }
    }
    
    var headers: [String: String]? {
        switch self {
        case .exchangeToken(_):
            return ["Content-Type": "application/json",
                    "Accept": "application/json"]
        }
    }
}

먼저 사용할 API enum을 만들어주고 exchangeToken case를 선언해줍니다.

그 다음 TargetAPI 프로토콜을 채택해주는데, 이는 Moya에 있는 protocol입니다.

baseURL, Path, Method, header는 URLSession에서도 많이 보았던 친구들이니 넘어가겠습니다.

- task는 parameter입니다.

- sampleData는 테스트 시 응답으로 받을 데이터를 설정하는 로직입니다.

 

github token api를 사용할 준비가 끝났습니다. 이제 이를 사용해서 token을 받아와 볼까요?

이를 사용하는 객체입니다.

import RxSwift
import Moya

protocol GitHubTokenExchangable {
    var provider: MoyaProvider<GithubAPI> { get }
    func exchangeToken(by code: String) -> Observable<String>
}

final class GithubTokenRepository: GitHubTokenExchangable {
    var provider = MoyaProvider<GithubAPI>()
    
    func exchangeToken(by code: String) -> Observable<String> {
        provider.rx
            .request(.exchangeToken(code))
            .filterSuccessfulStatusCodes()
            .map(Token.self)
            .map { $0.accessToken }
            .asObservable()
    }
}

조금있다 말씀드릴 테스트를 위해 GithubTokenExchangeable을 추상화하였습니다.

MoyaProvider<TargetType>을 활용하여 api를 사용할 수 있습니다.

이번 프로젝트에서 Rx를 사용하고 있기에 Moya와 Rx를 같이 사용해보았습니다.

이와 같이 선언한 provider에서 request함수 안에 통신할 api를 enum으로 선언하였던 case를 명시해줍니다.

그럼 Single<Response>타입이 오는데 이를 Token으로 Decoding해준 후 필요한 accessToken값을 가져와 리턴해줍니다.

URLSession과 비교하였을 때 코드량이 엄청나게 줄어들었음을 확인할 수 있습니다.

다만 http code별 에러 처리 등 상세한 에러 핸들링 등이 URLSession과 비교하였을 때의 단점인 것 같습니다.

 

테스트

개인적으로 Moya를 사용하는 가장 큰 이유는 테스트라고 생각합니다.

방금 저희가 구현한 sampleData의 코드를 확인해보면, 프로젝트 안에 MockAccessToken.json을 가져와 데이터로 담고 있습니다.

테스트 시 제가 보고 싶은 json을 넣어둔 것이죠.

그리고 이를 위해 GithubTokenExchangable 또한 추상화 해두었습니다.

 

실제 서버와 통신하지 않고 제가 지정한 값이 응답으로 오는 Stub객체를 구현해보겠습니다.

import Moya
import RxSwift

class GithubTokenRepositoryStub: GitHubTokenExchangable {
    var provider = MoyaProvider<GithubAPI>(stubClosure: MoyaProvider.immediatelyStub)
    
    func exchangeToken(by code: String) -> Observable<String> {
        provider.rx
            .request(.exchangeToken(code))
            .filterSuccessfulStatusCodes()
            .map(Token.self)
            .map { $0.accessToken }
            .asObservable()
    }
}

실제 코드와 다른점이 거의 없습니다.

바로 MoyaProvider의 생성자 인자값입니다.

stubClosure: MoyaProvider.immediatelyStub 인데요.

Moya 문서를 보면 3가지 값을 지정할 수 있습니다.

1. .never = 응답이 오지 않는 경우를 테스트한다.

2. .immedeiately = 즉시 응답이 오는 경우를 테스트한다. (제가 채택한 방식)

3. .delay = 지정한 시간이 지난 후에 응답이 오는 경우를 테스트한다. ( 실제 서버에 요청하고 데이터를 가져오기 까지 조금의 딜레이가 있는 상황에서 테스트를 하기 원하는 경우)

 

그리고 항상 200코드에서 데이터 응답이 온다고 합니다.

class GithubTokenExchangeTest: XCTestCase {
    var tokenExchangable: GitHubTokenExchangable!
    
    override func setUpWithError() throws {
        tokenExchangable = GithubTokenRepositoryStub()
    }
    
    func testTokenExchange() throws {
        let expectation = XCTestExpectation()
        guard let json = Bundle.main.path(forResource: "MockAccessToken", ofType: "json") else { return }
        guard let jsonString = try? String(contentsOfFile: json) else { return }
        guard let mockData = jsonString.data(using: .utf8) else { return }
        guard let expectedToken = try? JSONDecoder().decode(Token.self, from: mockData).accessToken else { return }
        
        tokenExchangable.exchangeToken(by: "code")
            .bind { token in
                XCTAssertEqual(expectedToken, token)
                expectation.fulfill()
            }
            .dispose()
        wait(for: [expectation], timeout: 1.0)
    }
}

그리하여 이와 같은 테스트코드를 작성하였습니다.

URLProtocol과 비교하면 정말 편하게(조금의 코드 작성으로) 사용할 수 있구나, 이게 Moya의 장점이구나 를 체감하였습니다.

나중에 라이브러리 선택 이유에서 누가 Moya를 사용하신 이유가 무엇인가요? 라고 여쭤보신다면,

"URLSession보다 코드 작성량이 적기에 비용이 적고 가독성은 높으면서 URLProtocol보다 테스트를 하기까지의 코드 작성량도 적어서 편합니다."라고 말할 것 같네요..!

 

 

감사합니다!

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함