티스토리 뷰
개인적으로 생각하는 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보다 테스트를 하기까지의 코드 작성량도 적어서 편합니다."라고 말할 것 같네요..!
감사합니다!
'iOS > 스위프트' 카테고리의 다른 글
[Swift][MVC] 애플 MVC 야무지게 사용해보기. (1) | 2022.07.19 |
---|---|
[Swift][DI] 의존성 주입 라이브러리. (0) | 2022.07.12 |
[iOS][HIG] HIG란 무엇일까? (0) | 2022.05.12 |
[Swift][Di][UnitTest] 의존성 주입하여 코드 테스트하기. (0) | 2022.04.23 |
[Swift] NotificationCenter란 무엇일까!? (0) | 2022.04.17 |