Skip to content

Instantly share code, notes, and snippets.

@arifinfrds
Created June 26, 2023 14:14
Show Gist options
  • Save arifinfrds/a8dd959b48ed6840e1c60e013fc1f1eb to your computer and use it in GitHub Desktop.
Save arifinfrds/a8dd959b48ed6840e1c60e013fc1f1eb to your computer and use it in GitHub Desktop.
//
// 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