Created
June 26, 2023 14:14
-
-
Save arifinfrds/a8dd959b48ed6840e1c60e013fc1f1eb to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// CharactersViewModelTests.swift | |
// RickyAndMortyCharacterTests | |
// | |
// Created by Arifin Firdaus on 13/02/23. | |
// | |
import Combine | |
import XCTest | |
@testable import RickyAndMortyCharacter | |
@MainActor final class CharactersViewModelTests: XCTestCase { | |
func test_init_doesNotRequestUseCase() { | |
let (_, loader) = makeSUT(charactersLoader: LoaderSpy()) | |
XCTAssertEqual(loader.messages, []) | |
} | |
func test_onLoad_requestUseCase() async { | |
let (sut, loader) = makeSUT(charactersLoader: LoaderSpy()) | |
await sut.onLoad(page: 1) | |
XCTAssertEqual(loader.messages, [ .loadCharacters(page: 1) ]) | |
} | |
func test_onLoad_inInitialState() async { | |
let (sut, _) = makeSUT(charactersLoader: LoaderSpy()) | |
await sut.onLoad(page: 1) | |
XCTAssertEqual(sut.isLoading, false) | |
XCTAssertEqual(sut.cellViewModels, []) | |
} | |
func test_searchText_filterByName() async { | |
let sut = makeSUT(charactersLoader: CharactersLoaderStub(result: .success([anyCharacter(name: "Arifin", createdDate: { self.testSpecificDate() }), anyCharacter(name: "Hello", createdDate: { self.testSpecificDate() })]))) | |
await sut.onLoad(page: 1) | |
sut.searchText = "e" | |
XCTAssertEqual(sut.cellViewModels, [ anyCharacter(name: "Hello", createdDate: { self.testSpecificDate() }).toViewModel() ]) | |
} | |
func test_onFilterBarButtonItemTapped_showFilterScreen() { | |
let (sut, _) = makeSUT(charactersLoader: LoaderSpy()) | |
sut.onFilterBarButtonItemTapped() | |
XCTAssertEqual(sut.route, .filterScreen) | |
} | |
func test_onFilterBarButtonItemTapped_dismissFilterScreen() { | |
let (sut, _) = makeSUT(charactersLoader: LoaderSpy()) | |
sut.onFilterApplyButtonTapped() | |
XCTAssertEqual(sut.route, nil) | |
} | |
func test_hasNextPage_loadNextPageItems() async { | |
let (sut, loader) = makeSUT(charactersLoader: LoaderSpy()) | |
await sut.onLoad(page: 1) | |
XCTAssertEqual(loader.messages, [ .loadCharacters(page: 1) ]) | |
await sut.onLoad(page: 2) | |
XCTAssertEqual(loader.messages, [ .loadCharacters(page: 1), .loadCharacters(page: 2) ]) | |
} | |
func test_hasNextPage_deliversNextPageItems() async { | |
let firstPageItem = anyCharacter(name: "First Page Item", createdDate: { self.testSpecificDate() }) | |
let loader = CharactersLoaderStub(result: .success([])) | |
let sut = makeSUT(charactersLoader: loader) | |
XCTAssertEqual(sut.isFirstTimeOpeningPage, true) | |
XCTAssertEqual(sut.cellViewModels.count, 0) | |
let firstPageInfo = Info(count: 0, pages: 2, next: "next", prev: nil) | |
loader.stub(response: .init(info: firstPageInfo, results: [ firstPageItem ])) | |
await sut.onLoad(page: 1) | |
XCTAssertEqual(sut.hasNextPage, true) | |
XCTAssertEqual(sut.currentPage, 1) | |
XCTAssertEqual(sut.cellViewModels.count, 1) | |
XCTAssertEqual(sut.isFirstTimeOpeningPage, false) | |
let secondPageItem = anyCharacter(name: "Second Page Item", createdDate: { self.testSpecificDate() }) | |
let lastPageInfo = Info(count: 0, pages: 2, next: nil, prev: nil) | |
loader.stub(response: .init(info: lastPageInfo, results: [ secondPageItem ])) | |
await sut.onLoad(page: 2) | |
XCTAssertEqual(sut.hasNextPage, false) | |
XCTAssertEqual(sut.currentPage, 2) | |
XCTAssertEqual(sut.cellViewModels.count, 2) | |
XCTAssertEqual(sut.isFirstTimeOpeningPage, false) | |
let allItems = [ firstPageItem, secondPageItem ] | |
sut.cellViewModels.enumerated().forEach { (index, item) in | |
XCTAssertEqual(item.name, allItems[index].name) | |
} | |
} | |
func test_searchText_searchingTextStartPagination() async { | |
let loader = CharactersLoaderStub(result: .success([anyCharacter(name: "Arifin", createdDate: { self.testSpecificDate() }), anyCharacter(name: "Hello", createdDate: { self.testSpecificDate() })])) | |
let sut = makeSUT(charactersLoader: loader) | |
await sut.onLoad(page: 1) | |
sut.searchText = "e" | |
XCTAssertEqual(sut.cellViewModels, [ anyCharacter(name: "Hello", createdDate: { self.testSpecificDate() }).toViewModel() ]) | |
let secondPageItem = anyCharacter(name: "Second Page Item", createdDate: { self.testSpecificDate() }) | |
let lastPageInfo = Info(count: 0, pages: 2, next: nil, prev: nil) | |
loader.stub(response: .init(info: lastPageInfo, results: [ secondPageItem ])) | |
await sut.onLoad(page: 2) | |
XCTAssertEqual(sut.cellViewModels.count, 2) | |
XCTAssertEqual(sut.cellViewModels, [ | |
anyCharacter(name: "Hello", createdDate: { self.testSpecificDate() }).toViewModel(), | |
anyCharacter(name: "Second Page Item", createdDate: { self.testSpecificDate() }).toViewModel() | |
]) | |
} | |
// MARK: - Helpers | |
private func makeSUT( | |
charactersLoader: LoaderSpy, | |
file: StaticString = #filePath, | |
line: UInt = #line | |
) -> (sut: CharactersViewModel, charactersLoader: LoaderSpy) { | |
let sut = CharactersViewModel(charactersLoader: charactersLoader) | |
trackMemoryLeak(on: sut, file: file, line: line) | |
return (sut, charactersLoader) | |
} | |
private func makeSUT( | |
charactersLoader: CharactersLoaderStub, | |
file: StaticString = #filePath, | |
line: UInt = #line | |
) -> CharactersViewModel { | |
let sut = CharactersViewModel(charactersLoader: charactersLoader) | |
trackMemoryLeak(on: sut, file: file, line: line) | |
return sut | |
} | |
private final class CharactersLoaderStub: CharactersLoader { | |
private let result: Result<[RemoteCharacter], Error> | |
private var response: GetCharacterResponse? | |
init(result: Result<[RemoteCharacter], Error>) { | |
self.result = result | |
} | |
func load(page: Int) async throws -> GetCharacterResponse { | |
if let response = self.response { | |
self.response = nil | |
return response | |
} else { | |
switch result { | |
case let .success(characters): | |
let info = Info(count: characters.count, pages: 1, next: nil, prev: nil) | |
return .init(info: info, results: characters) | |
case let .failure(error): | |
throw error | |
} | |
} | |
} | |
func stub(response: GetCharacterResponse) { | |
self.response = response | |
} | |
} | |
private final class LoaderSpy: CharactersLoader { | |
enum Message: Equatable { | |
case loadCharacters(page: Int) | |
} | |
private(set) var messages = [Message]() | |
func load(page: Int) async throws -> GetCharacterResponse { | |
self.messages.append(.loadCharacters(page: page)) | |
let info = Info(count: 0, pages: 1, next: "next link url", prev: nil) | |
return .init(info: info, results: []) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment