맛동산이

Moya Mock Data 사용하기(feat. test Double) 본문

앱/Swift

Moya Mock Data 사용하기(feat. test Double)

진ddang 2025. 1. 31. 10:03

회사 작업을 하다보면 api의 목데이터나, 혹은 배포가 되기 이전에 목api를 사용하고 싶을때가 존재한다.

이때 moya를 이용하고 있다면 목데이터를 직접 설정하여 사용할수 있다.

 

 

 

Moya 기초

moya는 alamofire를 래핑하고 있는 라이브러리로, TargetType이라는 프로토콜을 사용해서 상당히 간단하게 api를 작성하고 사용할수 있다.

moya에서 사용하는 중요한 개념들

  • Provider: Moya의 MoyaProvider는 모든 네트워크 서비스와 상호작용할때 사용할 객체
  • Target: Moya target은 일반적으로 전체 API 서비스를 설명합니다. 타겟은 서비스, 그 자체의 가능한 끝점, 요청을 이행하는 각 끝점에 요구되는 정보를 설명합니다. TargetType 프로토콜을 체택하는 것으로 target을 정의합니다.
  • Endpoint: Moya는 네트워크 요청을 이행하는 요구된 정보의 기본조각을 설명하는 반 내부(semi-internal) 끝점(Endpoint) 객체를 사용합니다(예를들어 HTTP method, request body, header 등) MoyaProvider는 모든 target들을 끝점(Endpoint)으로 변형(transforms)하고, 결국에는 원시 URLRequest로 변형되어 집니다.
final class func defualtEndpointMapping(for target: Target) -> Endpoint {
	return EndPoint(
		url: URL(target: target).absoluteString,
		sampleResponseClosure { .networkResponse(200, target.sampleData) },
		method: target.method,
		task: target.task,
		httpHeaderFields: target.headers
	)
}

이렇게 구현되어 있습니다.

 

 

 

Moya 사용하기

1️⃣ targetType작성

가장먼저 enum으로 사용할 api 목록을 만들어준다.

enum UserRouter { 
	case getUserInfo(memberID: String) 
	case postUser(userInfo: UserInfo)
}

 

2️⃣ 네트워크 요청/처리

  1. 프로바이더 생성
    private let provider = MoyaProvider<UserRouter>
    
    이때, 제네릭 타입으로 TargetType을 준수하는 Enum을 받게 됩니다.
  2. 작성한 targetType을 provider를 사용해서 실행할수 있습니다.
  3. 호출부 작성
    func getRecentUpdateCenter(_ parameters : DictionaryType = ["":""] ,   _ completion: @escaping (resModel?, Error?) -> Void) {
        rx.request(.getRecentUpdateCenterModel(parameters))
            .filterSuccessfulStatusCodes()
            .map(resModel.self)
            .subscribe(onSuccess: { completion($0, nil) }, onError: { completion(nil, $0) })
            .disposed(by: disposeBag)
    }
    
    rxMoya를 사용한다는 가정하에 저러한 코드를 통해서 해당 request함수를 구현
  4. 위에서 작성한 provider를 사용해서 요청을 보냄

 

MockData사용하기(feat, stubClosure)

 💡메소드 스텁(method stub): 소프트웨어 개발에 쓰이고 다른 프로그래밍 기능을 대리하는 코드

스텁(stubbed)는 어딘가에 찧다라는 뜻이다.

여기에서 알수 있듯, stubClosure는 어떠한 값의 대리하는 코드를 의미하며, moya에서는 결과값을 리턴하는 클로저를 의미한다.

 

1️⃣ provider에서 stubClosure를 생성해주기

subClosure는 다음과 같이 구현되어있다.

enum StubBehavior { 
	case never
	case immediate
	case delayed(seconds: TimeInterval)
}

extension MoayProvider { 
	static func neverStub(_: Target) -> Moay.StubBehavior { .never }
	static func immediatelyStub(_: Target) -> Moay.StubBehavior { .immediate }
	static func delayedStub(_: Target) -> Moay.StubBehavior { _ in .delayed(second: seconds) }
}

 

moyaProvier의 init을 살펴보면 다음가 같다.

  1. defaultEndPointMapping (이건위에서 다뤗다.)과 defualtRequestMapping을 통해서 targetType의 값을 주입해준다.
  2. stubClosure를 주입해준다. (기본값이 neverStub)
  3. callbackQueue
  4. session(만료시간)
  5. plugins
  6. trackInFlights

를 주입해줄수 있다.

우리는 이중에서 stubClosure에 immediate를 주입해준다.

let provider = MoyaProvider<직접만든TargetType>(stubClosure: .immediate)

 

2️⃣ targetType에서 sampleData 추가하기

샘플데이터를 바로 작성해도 되지만 폴더링을 위해서 새로운 .json파일을 만들어서 관리한다는 측면에서는 다음과 같은 과정으로 해결하게 됩니다.

  1. 데이터 찾아오기
extension MockJSON {
	func load(_ fileName: String) -> Data? {
		guard let fileURL = Bundle.main.url(forResource: fileName, withExtension: "json")
		else { return nil }
	}
}
  1. 원하는 데이터 타입으로 매핑 하기
extension MockJSON {
	static func decode<T: Decodable>(jsonData: Data) -> T? {
		guard let response = try? JSONDecoder().decode(원하는타입<T>.self, from: jsonData)
		else { return nil }
		return response
	}
}
  1. SampleData 구현해주기
extension TargetType {
	var samepleData: Data {
		switch self { 
			case .getMethod:
				return MockJSON.load(fileName: "파일명") ?? Data()
			}
		}
	}

이렇게하면, stubClosure를 통해서 바로 JSON의 데이터가 내려오게 된다.

 

추가적으로 왜 StubClosure일까?

stub이라는것에 대한 이유를 전혀 모르겠었는데 test코드에 대해서 공부를 하다보다가 test double이라는것에 대해서 알게되었고, stub 의 이유가 여기 있었다.

 

TestDouble이란

실제 객체를 대신하여 테스팅하는 모든 방법에 대한 것이다.

(이는 위험한 일을 대신하는 스턴트맨에서 파생된말로, 실제로는 스턴트 더블이라고 한다. )

아무튼 TestDouble에는 5개의 객체 가 있다.

 

1. Dummy

인스턴스화 된 객체지만, 실제로 동작은 확인할 필요가 없을때 사용하는 객체

더미객체가 전달되더라도 실제로 함수가 호출될때 정상동작인지는 알수가 없다.

 

2. Fake

동작을 하지만 실제 동작처럼 정교하지는 않는 그러한 객체

예를들어, 디비와 연결되어야 하는데 실제 디비는 너무 복잡하기때문에 굉장히 간단한 디비를 넘겨준다던지.

 

3. Stub

stub은 메소드가 호출될때 실제로 동작하는것처럼 값을 넘겨주는 객체이다.

즉 결과값에 대한 정의가 되겠다.

이를 통해 해당 메소드가 동작을 하는지 확인하기 위한 객체

 

4. Mock

mock과 stub을 많이 혼돈하여 사용하는것 같다고 개인적으로 느끼는데

stub이 결과를 확인하기 위한 객체라면, mock은 호출에 대한 객체이다.

내려온 데이터와 예상하는 데이터가 동일한지를 확인하는 객체

 

5. Spy

객체를 복사해서 테스트에 사용하는 객체

 

Stub과 Mock의 명확한 차이

Stub과 Mock의 차이는

Stub은 테스트를 위해 의도된 결과값이 반환되고, 메소드의 기능을 확인하는것

mock은 호출된 결과값과 기대값이 동일한지 일치를 확인하는것이다.

  • 우리가 백엔드에서 넣어주는 데이터와 명세된 데이터를 비교하는것은 mock데이터가 맞고
  • 단순히 api가 동작을 잘 하는지에 대한 데이터를 넣어주는것이라면 stub이다.

참고자료

https://velog.io/@lyh990517/TestDouble이-뭐죠

https://devmjun.github.io/archive/Moya-Tutorial#google_vignette

https://omubdc.tistory.com/9

 

반응형