본문 바로가기
감정일기(가칭)

처음 보는 사람도 다음엔 혼자 Mock을 만들 수 있게...설계가이드

by 밤새는 탐험가89 2025. 10. 24.
728x90
SMALL

🧪 MockDiaryStore란?

실제 DB(Core Data)나 파일 I/O 없이도 ViewModel 로직만 빠르게 테스트하려고 만든 가짜(Store 대역) 입니다.

 

- 실제 앱: DiaryStore ← Core Data와 통신

- 테스트: MockDiaryStore ← 메모리 값만 가지고 흉내

 

즉, DiaryProviding 프로토콜을 구현하되, 내부 동작은 간단한 배열/고정값으로 대체합니다.


🧱 만들기 레시피 (외워두기)

- “대상 인터페이스(프로토콜)”를 고른다 → DiaryProviding

- 그 프로토콜을 채택하는 테스트 전용 클래스를 만든다 → MockDiaryStore

- 테스트가 원하는 가짜 데이터 저장소(배열/딕셔너리 등)를 만든다 → mockDiaries, mockImages

- 프로토콜 요구 메서드·프로퍼티를 그 가짜 데이터로 채워서 리턴한다

 

최소 예시

import Combine
import UIKit
@testable import LemonLog

@MainActor
final class MockDiaryStore: DiaryProviding {

    // 1) 테스트에서 주입/세팅할 가짜 데이터
    var mockDiaries: [EmotionDiaryModel] = []
    var mockImages: [(UIImage?, String)] = []

    // 2) 프로토콜 요구사항 구현
    var diariesPublisher: AnyPublisher<[EmotionDiaryModel], Never> {
        Just(mockDiaries).eraseToAnyPublisher()   // 👉 아래 “왜 Just?” 참고
    }

    var snapshot: [EmotionDiaryModel] { mockDiaries }

    func diary(with id: String) -> EmotionDiaryModel? {
        mockDiaries.first { $0.id.uuidString == id }
    }

    func diaries(inWeekOf date: Date) -> [EmotionDiaryModel] {
        mockDiaries
    }

    func countByEmotion(inWeekOf date: Date) -> [EmotionCategory: Int] {
        Dictionary(grouping: mockDiaries) { EmotionCategory(rawValue: $0.emotion) ?? .happy_grade_1 }
            .mapValues(\.count)
    }

    func fetchFirstImages() async -> [(UIImage?, String)] {
        mockImages
    }
}

❓자주 묻는 것들

1) LemonLog.EmotionDiaryModel로 써도 돼?

네. 같아요.
@testable import LemonLog 되어 있으면

보통 EmotionDiaryModel로 바로 씁니다.


네이밍 충돌이 있거나 명시적으로 표시하고 싶을 때만

LemonLog.EmotionDiaryModel처럼 모듈 접두사를 붙이세요.

 

2) Just는 왜 쓰는 거야?

Just는 “값 하나를 즉시 한 번만 방출(emit)하고 완료하는” 간단한 Publisher예요.

 

모의 환경에서 “초기값 한 번만 흘려보내면 되는” 경우에 딱 좋아요.

diariesPublisher가 “현재 리스트는 이거야” 라고

한 번만 알려주면 충분한 테스트(예: 최근 5개 제한, 통계 계산)에서 가볍고 빠릅니다.

 

그럼 실시간 갱신 테스트는?

 

저장/수정/삭제처럼 여러 번 갱신되는 흐름까지 테스트하고 싶다면 Just 대신:

- CurrentValueSubject<[EmotionDiaryModel], Never> (현재값 보관 + 여러 번 보냄)

- PassthroughSubject<[EmotionDiaryModel], Never> (현재값 보관 없음, 보낼 때만 방출)
중 하나로 바꾸고, 테스트 중간중간 subject.send(newValue)로 여러 번 값을 내보내세요.

final class MockDiaryStoreStreaming: DiaryProviding {
    private let subject = CurrentValueSubject<[EmotionDiaryModel], Never>([])
    var diariesPublisher: AnyPublisher<[EmotionDiaryModel], Never> { subject.eraseToAnyPublisher() }
    var snapshot: [EmotionDiaryModel] { subject.value }

    // 테스트에서 호출할 도우미
    func push(_ diaries: [EmotionDiaryModel]) { subject.send(diaries) }

    // 나머지 프로토콜도 동일하게 채움…
}

 

3) diariesPublisher는 무슨 역할?

“일기 배열이 바뀔 때마다 구독자에게 알려주는 스트림” 입니다.

 

- 실제 앱에선 DiaryStore가 Core Data 변화를 감지해 send()로 내보냅니다.

- ViewModel(HomeViewModel)은 이 Publisher를 구독해서 UI 상태(@Published) 를 갱신하죠.

- Mock에서는 Just로 한 번만 내보내거나, Subject로 여러 번 내보내며 상황을 시뮬레이션합니다.


✅ 요약

- MockDiaryStore = 진짜 Store의 “가짜” (DB 없이 빠르게 ViewModel만 검증)

- Just = 값 1회 방출(간단한 초기 검증에 최적)

- 실시간 갱신 검증은 Subject로 바꾸고 send() 호출

- diariesPublisher = ViewModel이 실시간으로 상태를 받는 통로

- 타입 표기는 EmotionDiaryModel / LemonLog.EmotionDiaryModel 둘 다 OK (상황에 맞게)

728x90
LIST