Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Moya Network 모듈 세팅 #11

Merged
merged 10 commits into from
Jul 20, 2023
51 changes: 51 additions & 0 deletions Targets/KeymeKit/Sources/Network/APIs/KeymeAPI.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//
// KeymeAPI.swift
// Keyme
//
// Created by Young Bin on 2023/07/16.
// Copyright © 2023 team.humanwave. All rights reserved.
//

import Moya
import Foundation

public enum KeymeAPI {
case test
}

extension KeymeAPI: TargetType {
// TODO: 임시 - 서버 도메인 확정되면 변경할 것
public var baseURL: URL {
return URL(string: "https://randomuser.me")!
enebin marked this conversation as resolved.
Show resolved Hide resolved
}

// TODO: 임시 - API 추가될 떄마다 변경
public var path: String {
switch self {
case .test:
return "/api"
}
}

public var method: Moya.Method {
switch self {
case .test:
return .get
}
}

public var task: Task {
switch self {
case .test:
return .requestPlain
}
}

public var headers: [String: String]? {
return ["Content-type": "application/json"]
}

public var sampleData: Data {
return Data()
}
}
71 changes: 71 additions & 0 deletions Targets/KeymeKit/Sources/Network/NetworkManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//
// NetworkManager.swift
// Keyme
//
// Created by Young Bin on 2023/07/16.
// Copyright © 2023 team.humanwave. All rights reserved.
//

import Combine

import Moya
import CombineMoya

protocol MoyaNetworking {
associatedtype APIType: TargetType

/// Swift concurrency 맥락에서 네트워크 요청
func request(_ api: APIType) async throws -> Response

/// Combine 맥락에서 네트워크 요청
func request(_ api: APIType) -> AnyPublisher<Response, MoyaError>

/// 인증토큰 헤더에 넣어주는 메서드
mutating func registerAuthorizationToken(_ token: String)
enebin marked this conversation as resolved.
Show resolved Hide resolved
}

public struct NetworkManager {
public static let shared = NetworkManager()

private(set) var provider: MoyaProvider<KeymeAPI>

init(provider: MoyaProvider<KeymeAPI> = .init()) {
self.provider = provider
}
}

extension NetworkManager: MoyaNetworking {
public func request(_ api: KeymeAPI) async throws -> Response {
try await withCheckedThrowingContinuation { continuation in
provider.request(api) { result in
switch result {
case let .success(response):
continuation.resume(returning: response)
enebin marked this conversation as resolved.
Show resolved Hide resolved
case let .failure(error):
continuation.resume(throwing: error)
}
}
}
}

public func request(_ api: KeymeAPI) -> AnyPublisher<Response, MoyaError> {
provider.requestPublisher(api)
}

public mutating func registerAuthorizationToken(_ authorizationToken: String) {
self.provider = MoyaProvider<KeymeAPI>(endpointClosure: endpointClosure(with: authorizationToken))
}
}

private extension NetworkManager {
func endpointClosure(with token: String) -> MoyaProvider<KeymeAPI>.EndpointClosure {
return { (target: KeymeAPI) -> Endpoint in
let defaultEndpoint = MoyaProvider.defaultEndpointMapping(for: target)

switch target {
case .test:
return defaultEndpoint.adding(newHTTPHeaderFields: ["Authorization": token])
}
}
}
}
141 changes: 141 additions & 0 deletions Targets/KeymeKit/Tests/Network/NetworkManagerTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
//
// NetworkManagerTests.swift
// KeymeTests
//
// Created by Young Bin on 2023/07/16.
// Copyright © 2023 team.humanwave. All rights reserved.
//

import XCTest

import Moya
enebin marked this conversation as resolved.
Show resolved Hide resolved

@testable import KeymeKit

final class NetworkManagerTests: XCTestCase {
var networkManager: NetworkManager!

override func setUpWithError() throws {
let stubbedProvider = MoyaProvider<KeymeAPI>(stubClosure: MoyaProvider.immediatelyStub)
networkManager = NetworkManager(provider: stubbedProvider)
}

override func tearDownWithError() throws {
networkManager = nil
}

func testRequestSuccess() async {
let testTarget: KeymeAPI = .test
let expectedResponseData = testTarget.sampleData

do {
let response = try await networkManager.request(testTarget)
let responseData = response.data
XCTAssertEqual(responseData, expectedResponseData, "Response data does not match expected data.")
} catch {
XCTFail("Request failed with error: \(error)")
}
}

func testRequestFailure() async {
let stubbedError = MoyaError.stringMapping(Response(statusCode: 400, data: Data()))

let endpointClosure = { (target: KeymeAPI) -> Endpoint in
return Endpoint(url: URL(target: target).absoluteString,
sampleResponseClosure: { .networkError(stubbedError as NSError) },
method: target.method,
task: target.task,
httpHeaderFields: target.headers)
}

let stubbedProvider = MoyaProvider<KeymeAPI>(
endpointClosure: endpointClosure,
stubClosure: MoyaProvider.immediatelyStub)
networkManager = NetworkManager(provider: stubbedProvider)

let testTarget: KeymeAPI = .test
do {
_ = try await networkManager.request(testTarget)
XCTFail("Request should have failed but it succeeded.")
} catch {
return
}
}

func testCombineRequestSuccess() {
let testTarget: KeymeAPI = .test
let expectedResponseData = testTarget.sampleData

let expectation = XCTestExpectation(description: "Receive response")
let cancellable = networkManager.request(testTarget)
.sink { completion in
switch completion {
case .finished:
break
case .failure(let error):
XCTFail("Request failed with error: \(error)")
}
expectation.fulfill()
} receiveValue: { response in
let responseData = response.data
XCTAssertEqual(responseData, expectedResponseData, "Response data does not match expected data.")
}

wait(for: [expectation], timeout: 1)
cancellable.cancel()
}

func testCombineRequestFailure() {
let stubbedError = MoyaError.stringMapping(Response(statusCode: 400, data: Data()))

let endpointClosure = { (target: KeymeAPI) -> Endpoint in
return Endpoint(url: URL(target: target).absoluteString,
sampleResponseClosure: { .networkError(stubbedError as NSError) },
method: target.method,
task: target.task,
httpHeaderFields: target.headers)
}

let stubbedProvider = MoyaProvider<KeymeAPI>(
endpointClosure: endpointClosure,
stubClosure: MoyaProvider.immediatelyStub)
networkManager = NetworkManager(provider: stubbedProvider)

let testTarget: KeymeAPI = .test

let expectation = XCTestExpectation(description: "Receive response")
let cancellable = networkManager.request(testTarget)
.sink { completion in
switch completion {
case .finished:
XCTFail("Request should have failed but it succeeded.")
case .failure(let error):
break
}
expectation.fulfill()
} receiveValue: { _ in
XCTFail("Request should have failed but it received a value.")
}

wait(for: [expectation], timeout: 1.0)
cancellable.cancel()
}

func testRegisterAuthorizationToken() {
let testTarget: KeymeAPI = .test
let token = "test_token"

var endpoint = networkManager.provider.endpointClosure(testTarget)
XCTAssertNil(
endpoint.httpHeaderFields?["Authorization"],
"Authorization token should not exist.")

networkManager.registerAuthorizationToken(token)

endpoint = networkManager.provider.endpointClosure(testTarget)
XCTAssertEqual(
endpoint.httpHeaderFields?["Authorization"],
token,
"Authorization token does not match expected token.")
}
}
3 changes: 2 additions & 1 deletion Tuist/Dependencies.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import Foundation
import ProjectDescription
let dependencies = Dependencies(
swiftPackageManager: [
.remote(url: "https://github.com/firebase/firebase-ios-sdk", requirement: .upToNextMajor(from: "10.11.0"))
.remote(url: "https://github.com/firebase/firebase-ios-sdk", requirement: .upToNextMajor(from: "10.11.0")),
.remote(url: "https://github.com/Moya/Moya", requirement: .upToNextMajor(from: "15.0.3"))
],
platforms: [.iOS]
)
10 changes: 8 additions & 2 deletions Tuist/ProjectDescriptionHelpers/Project+Templates.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@ extension Project {
/// Helper function to create the Project for this ExampleApp
public static func app(name: String, platform: Platform, additionalTargets: [String]) -> Project {
var dependencies = additionalTargets.map { TargetDependency.target(name: $0) }
dependencies.append(.external(name: "FirebaseMessaging"))
dependencies += [
.external(name: "FirebaseMessaging")
]

var targets = makeAppTargets(
name: name,
platform: platform,
dependencies: dependencies)

targets += additionalTargets.flatMap({ makeFrameworkTargets(name: $0, platform: platform) })
return Project(name: name,
organizationName: Environment.organizationName,
Expand All @@ -35,7 +38,10 @@ extension Project {
infoPlist: .default,
sources: ["Targets/\(name)/Sources/**"],
resources: [],
dependencies: [])
dependencies: [
.external(name: "Moya"),
.external(name: "CombineMoya")
])

let tests = Target(
name: "\(name)Tests",
Expand Down