ReactorKit์ ๊ณต๋ถํ๊ธฐ ์ํด์ ์ ์ ์งํํ๋ ํ๋ก์ ํธ๋ค.
๊ฐ๋จํ ์ฑ์ด๋ค.
๋๋ค ์ ์ API๋ฅผ ํตํด์ 100๋ช ์ ์ฌ๋๋ค์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ณ
ํด๋น ์ธ๋ฌผ๋ค์ ํน์ ํ ์นดํ ๊ณ ๋ฆฌ๋ก ๋ถ๋ฅํด ๋ณด์ฌ์ฃผ๋ ์ฑ์ด๋ค.
๊ทธ๋ ๊ธฐ์ ํ ์คํธํด์ผ ํ๋ ๊ฒ๋ ๊ฐ๋จํ๋ค.
*UI ํ ์คํธ๋ ์งํํ์ง ์์๋ค.
ํ ์คํธ ํ๊ธฐ ์
๊ฐ์ฅ ๋จผ์ ์ด๋ค ๊ฑธ ํ ์คํธํด์ผ ํ ์ง ์ ํด๋ณด์.
ํด๋น ํ๋ก์ ํธ์๋ ๋ฆฌ์กํฐ๊ฐ ๋๊ฐ์ง๋ค.
ํ๋๋ ๋ฉ์ธํ๋ฉด์์ ์ฌ์ฉ๋๋ ๋ฆฌ์กํฐ
ํ๋๋ ๊ฐ ์นดํ ๊ณ ๋ฆฌ์ ์ง์ ํ์ ๋ ์ฌ์ฉ๋๋ ๋ฆฌ์กํฐ
๋ ๋ฆฌ์กํฐ์์ ํน์ ํ ์ก์ ์ ์ทจํ์ ๊ฒฝ์ฐ, ์ํ๋ ๋๋ก State๊ฐ ๋ณ๊ฒฝ๋๋์ง ํ์ธํ๋ฉด ๋๋ค.
๋ฉ์ธ ๋ฆฌ์กํฐ
๋ฉ์ธ ๋ฆฌ์กํฐ๋ 3๊ฐ์ง์ ์ก์ ์ ๊ฐ์ง๊ณ ์๋ค.
1. ์ ์ ๋ฐ์ดํฐ ๋ก๋
2. ๋ํ ์ผ ํ๋ฉด ํธ์
3. ํธ์๋ ๋ทฐ์ปจํธ๋กค๋ฌ๋ฅผ nil ์ฒ๋ฆฌํ๊ธฐ
์งํํ ๋ชจ๋ ํ ์คํธ์์๋ ๋ฐ์ดํฐ๋ฅผ ๋ก๋ํด์ผ ํ๋ ๊ณตํต๋ ์์ ์ด ํ์ํ๋ค.
๋ก๋ํ๊ธฐ๊ฐ ๋ฒ๊ฑฐ๋กญ๋ค๋ฉด mock ๋ฐ์ดํฐ๋ฅผ ๋ง๋ค์ด๋ ์ข๋ค.
์๋ฌดํผ,
์ด๋ฅผ ์ํด์ setUp ๋ฉ์๋์ ๋ก๋์์ ์ ๋ฏธ๋ฆฌ ํด์ผ ํ๋ค.
setUp์ ๊ฐ ํ ์คํธ ๋ฉ์๋๊ฐ ์งํ๋๊ธฐ ์ด์ ์ ํธ์ถ๋๋ ๋ฉ์๋์ด๋ค.
( ↔๏ธ tearDown์ ํ ์คํธ ๋ฉ์๋๊ฐ ๋ง๋ฌด๋ฆฌ๋ ํ ํธ์ถ๋๋ค )
์ฌ๊ธฐ์ ์ถ๊ฐ์ ์ผ๋ก ๋ก๋๋ ์๊ฐ์ ๋ฒ๊ธฐ ์ํด์ expectaion์ ์ด์ฉํ๋ค.
override func setUp() {
mainReactor = MainReactor()
let expectation = self.expectation(description: "Initial data load")
// ๋ฐ์ดํฐ๊ฐ ๋ก๋๋ ํ์ ํ
์คํธ๋ฅผ ์งํํ ์ ์๋๋ก ๊ตฌ๋
์ค์
mainReactor?.state
.map { $0.sectionData }
.distinctUntilChanged()
.skip(1) // ์ฒซ ๋ฒ์งธ๋ ์ด๊ธฐ ๊ฐ์ด๋ฏ๋ก skip
.subscribe(onNext: { [weak self] sectionData in
guard let self = self, sectionData.count > 0 else {
return
}
expectation.fulfill()
})
.disposed(by: disposeBag)
mainReactor?.action.onNext(.load(100))
wait(for: [expectation], timeout: 3.0)
}
โ wait ๋ฉ์๋๋ expectation ๋ฐฐ์ด์ ์๋ ๋ชจ๋ expectation์ด fulfill ๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฐ๋ค.
โ timeout์ 3์ด๋ก ์ ํํด ๋๋ฉด 3์ด๊ฐ ๋๋ ์์ ์ fulfill ๋์ง ์์ ๋ชจ๋ expectation์ด fulfill ๋๊ฒ ๋๋ค.
(1) load
ํน์ ํ ์นด์ดํธ๋งํผ์ ๋ฐ์ดํฐ๋ฅผ ์์ฒญํ์ ๊ฒฝ์ฐ, ์ ์์ ์ผ๋ก ํด๋น ๊ฐ์๋งํผ์ ๋ฐ์ดํฐ๊ฐ ๋ค์ด์ค๊ณ SectionData๊ฐ ๋ณ๊ฒฝ๋์๋์ง ํ์ธํด์ผ ํ๋ค. ( SectionData๋ ๊ฐ ์น์ ๋ณ ์นดํ ๊ณ ๋ฆฌ ์ด๋ฆ์ ๋ฐ๋ฅธ ์ฌ๋๋ค์ ์ ๋ฆฌํ ๋์ ๋๋ฆฌ๋ค )
์๋์ ๊ฐ์ด ์ฝ๋๋ฅผ ์์ฑํ๋ค. ๋์ ๋๋ฆฌ์ ๋ฐ์ดํฐ์๋ ์ค๋ณต๋ ๋ฐ์ดํฐ๊ฐ ์กด์ฌํ๋ฏ๋ก set์ ํตํด ์ค๋ณต์ ์ ๊ฑฐํด์ผ ํ๋ค.
func testMainReactorLoad() throws {
let expectation = self.expectation(description: "Section Data is changed")
var counts = 0
mainReactor?.state
.map { $0.sectionData }
.distinctUntilChanged()
.subscribe(onNext: { [weak self] sectionData in
guard let self = self, sectionData.count > 0 else {
return
}
var values: Set<PersonInfoDetail> = []
for (_, value) in sectionData {
values.formUnion(value)
}
let totalCount = values.count
counts = totalCount
expectation.fulfill()
})
.disposed(by: disposeBag)
wait(for: [expectation], timeout: 3.0)
XCTAssertEqual(counts, 100)
}
(2) toSectionDetail
load๋ฅผ ํตํด ๋ค์ด์จ ๋ฐ์ดํฐ ์ค ์ผ๋ถ ์ฌ๋๋ค ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํ์ ๊ฒฝ์ฐ, ์ํ์ pushingViewController๊ฐ ๋ณ๊ฒฝ๋์๋์ง ์ฆ, nil์ด ์๋๊ฒ ๋์๋์ง ํ์ธํด์ผ ํ๋ค. XCAssertNotNil์ ํตํด ๊ฐํธํ๊ฒ nil์ด ์๋์ง ํ์ธํ ์ ์๋ค.
func testMainReactorToSectionDetail() throws {
let expectation = self.expectation(description: "PushingViewController is changed")
mainReactor?.action.onNext(.toSectionDetail(mainReactor?.currentState.sectionData["20๋"] ?? []))
mainReactor?.state
.map { $0.pushingViewController }
.subscribe(onNext: { value in
expectation.fulfill()
})
.disposed(by: disposeBag)
wait(for: [expectation], timeout: 3.0)
XCTAssertNotNil(mainReactor?.currentState.pushingViewController)
}
(3) removePushed
pushingViewController์ ๊ฐ์ด ์๋์ง ํ์ธํ๊ณ , removePushed ์ก์ ์ ์์ฒญํ์ ๋, ์ ์์ ์ผ๋ก pushingViewController๊ฐ nil์ด ๋์ด์๋์ง ํ์ธํด์ผ ํ๋ค.
func testMainReactorRemovePushed() throws {
let expectation = self.expectation(description: "PushingViewController is changed")
mainReactor?.action.onNext(.removePushed)
mainReactor?.state
.map { $0.pushingViewController }
.subscribe(onNext: { _ in
expectation.fulfill()
})
.disposed(by: disposeBag)
wait(for: [expectation], timeout: 3.0)
XCTAssertNil(mainReactor?.currentState.pushingViewController)
}
๋ํ
์ผ ๋ฆฌ์กํฐ
๋ํ ์ผ ๋ฆฌ์กํฐ์๋ ํ๋์ ์ก์ ์ด ์๋ค.
ํน์ ํ ์ฌ๋์ ํด๋ฆญํ ๊ฒฝ์ฐ, ๊ทธ ๊ทผ์ฒ์ ์๋ ์ฌ๋์ ํ์ธํ ์ ์๋ ์ก์ ์ด ์๋ค.
๋ฐ๋ผ์, State์๋ expandedIndexPath๋ฅผ ๊ฐ์ง๊ณ ์๋ค.
toggle(IndexPath)
ํน์ ํ IndexPath๋ฅผ ์ ๋ฌํ์ ๊ฒฝ์ฐ, ์ํ์ expandedIndexPath๊ฐ ๋ณ๊ฒฝ๋์๋์ง ํ์ธํด๋ด์ผ ํ๋ค.
func testDetailReactor() throws {
let expectation = self.expectation(description: "IndexPath is changed")
let detailReactor: DetailReactor = DetailReactor(people: mainReactor!.currentState.sectionData["20๋"]!)
let indexPath: IndexPath = IndexPath(row: 0, section: 0)
detailReactor.action.onNext(.toggle(indexPath))
detailReactor.state
.map { $0.expandedIndexPath }
.distinctUntilChanged()
.subscribe(onNext: { _ in
expectation.fulfill()
})
.disposed(by: disposeBag)
wait(for: [expectation], timeout: 3.0)
XCTAssertNotNil(detailReactor.currentState.expandedIndexPath)
}
'iOS > Swift' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Swift] Cocoapods๋ง ์ง์ํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ SPM์ผ๋ก ์ ํํ๊ธฐ ( feat. naverMap ) (0) | 2024.07.30 |
---|---|
[Swift] ๋ฉ๋ชจ๋ฆฌ ๋์๋ฅผ ํด๊ฒฐํด๋ณด์(feat.map) (0) | 2024.02.20 |
[Swift] ์๋ชป๋ Coordinator ์ฌ์ฉ ๊ทธ๋ฆฌ๊ณ ๋ฆฌํฉํ ๋ง (0) | 2024.01.16 |
[iOS] UITableView์์ ์ ์ค์ฒ ์ฌ์ฉํ๊ธฐ / ์ด์์ ๋ฆฌ (1) | 2023.07.20 |
[Swift] Extension์ ๋ํด์ ์์๋ณด์. (1) | 2023.04.13 |