app-reviews/Frameworks/Feed/Test/Tests/View Models/FeedListViewModelTests.swift
Javier Cicchelli c150b88ddc [Framework] Feed List improvements (#19)
This PR contains the work done to improve the overall implementation of the `FeedListViewModel` view model in the `Feed` framework by utilising unit tests.

Reviewed-on: #19
Co-authored-by: Javier Cicchelli <javier@rock-n-code.com>
Co-committed-by: Javier Cicchelli <javier@rock-n-code.com>
2024-03-22 14:44:52 +00:00

582 lines
15 KiB
Swift

//
// FeedListViewModelTests.swift
// ReviewsFeedTests
//
// Created by Javier Cicchelli on 22/03/2024.
// Copyright © 2024 Röck+Cöde. All rights reserved.
//
import Combine
import ReviewsFoundationKit
import ReviewsiTunesKit
import XCTest
@testable import ReviewsFeed
final class FeedListViewModelTests: XCTestCase {
// MARK: Constants
private let coordination: FeedListCoordinationSpy = .init()
// MARK: Properties
private var sut: FeedListViewController.ViewModel!
private var cancellables: Set<AnyCancellable> = []
// MARK: Setup
override func setUp() async throws {
sut = .init(
configuration: .init(session: .mock),
coordination: coordination
)
}
override func tearDown() async throws {
cancellables.removeAll()
}
// MARK: Initialisers tests
func testInit() {
// GIVEN
// WHEN
// THEN
XCTAssertEqual(sut.filter, .all)
XCTAssertFalse(sut.isFilterEnabled)
XCTAssertFalse(sut.isFiltering)
XCTAssertFalse(sut.isLoading)
XCTAssertFalse(sut.isWordsShowing)
XCTAssertTrue(sut.items.isEmpty)
XCTAssertEqual(sut.itemsCount, 0)
XCTAssertEqual(sut.state, .initial)
XCTAssertTrue(sut.words.isEmpty)
}
// MARK: Functions tests
func testFetch_allItems_whenResponseOK_withSomeItems() async {
let expectation = XCTestExpectation()
var isLoading: [Bool] = []
// GIVEN
MockURLProtocol.response = .init(
statusCode: 200,
object: Feed(entries: .sample)
)
// WHEN
sut.$isLoading
.collect(3)
.sink { value in
isLoading.append(contentsOf: value)
expectation.fulfill()
}
.store(in: &cancellables)
await sut.fetch()
wait(for: [expectation], timeout: 2)
// THEN
XCTAssertEqual(isLoading, [false, true, false])
XCTAssertTrue(sut.isFilterEnabled)
XCTAssertFalse(sut.isWordsShowing)
XCTAssertFalse(sut.items.isEmpty)
XCTAssertEqual(sut.items.count, 5)
XCTAssertEqual(sut.itemsCount, 5)
XCTAssertEqual(sut.state, .populated)
}
func testFetch_allItems_whenResponseOK_withNoItems() async {
let expectation = XCTestExpectation()
var isLoading: [Bool] = []
// GIVEN
MockURLProtocol.response = .init(
statusCode: 200,
object: Feed(entries: .none)
)
// WHEN
sut.$isLoading
.collect(3)
.sink { value in
isLoading.append(contentsOf: value)
expectation.fulfill()
}
.store(in: &cancellables)
await sut.fetch()
wait(for: [expectation], timeout: 2)
// THEN
XCTAssertEqual(isLoading, [false, true, false])
XCTAssertFalse(sut.isFilterEnabled)
XCTAssertFalse(sut.isWordsShowing)
XCTAssertTrue(sut.items.isEmpty)
XCTAssertEqual(sut.itemsCount, 0)
XCTAssertEqual(sut.state, .empty)
}
func testFetch_allItems_whenResponseNotOK() async {
let expectation = XCTestExpectation()
var isLoading: [Bool] = []
// GIVEN
MockURLProtocol.response = .init(statusCode: 404)
// WHEN
sut.$isLoading
.collect(3)
.sink { value in
isLoading.append(contentsOf: value)
expectation.fulfill()
}
.store(in: &cancellables)
await sut.fetch()
wait(for: [expectation], timeout: 2)
// THEN
XCTAssertEqual(isLoading, [false, true, false])
XCTAssertFalse(sut.isFilterEnabled)
XCTAssertFalse(sut.isWordsShowing)
XCTAssertTrue(sut.items.isEmpty)
XCTAssertEqual(sut.itemsCount, 0)
XCTAssertEqual(sut.state, .error)
}
func testFetch_filteredItems_whenResponseOK_withSomeItems() async {
let expectation = XCTestExpectation()
var isLoading: [Bool] = []
// GIVEN
MockURLProtocol.response = .init(
statusCode: 200,
object: Feed(entries: .sample)
)
// WHEN
sut.$isLoading
.collect(3)
.sink { value in
isLoading.append(contentsOf: value)
expectation.fulfill()
}
.store(in: &cancellables)
sut.filter(by: .only1Star)
await sut.fetch()
wait(for: [expectation], timeout: 2)
// THEN
XCTAssertEqual(isLoading, [false, true, false])
XCTAssertEqual(sut.filter, .only1Star)
XCTAssertTrue(sut.isFilterEnabled)
XCTAssertTrue(sut.isWordsShowing)
XCTAssertFalse(sut.items.isEmpty)
XCTAssertEqual(sut.items.count, 1)
XCTAssertEqual(sut.itemsCount, 2)
XCTAssertEqual(sut.state, .populated)
XCTAssertFalse(sut.words.isEmpty)
XCTAssertEqual(sut.words.count, 3)
}
func testFetch_filteredItems_whenResponseOK_withNoItems() async {
let expectation = XCTestExpectation()
var isLoading: [Bool] = []
// GIVEN
MockURLProtocol.response = .init(
statusCode: 200,
object: Feed(entries: .none)
)
// WHEN
sut.$isLoading
.collect(3)
.sink { value in
isLoading.append(contentsOf: value)
expectation.fulfill()
}
.store(in: &cancellables)
sut.filter(by: .only1Star)
await sut.fetch()
wait(for: [expectation], timeout: 2)
// THEN
XCTAssertEqual(isLoading, [false, true, false])
XCTAssertEqual(sut.filter, .only1Star)
XCTAssertFalse(sut.isFilterEnabled)
XCTAssertFalse(sut.isWordsShowing)
XCTAssertTrue(sut.items.isEmpty)
XCTAssertEqual(sut.itemsCount, 0)
XCTAssertEqual(sut.state, .empty)
XCTAssertTrue(sut.words.isEmpty)
}
func testFetch_filteredItems_whenResponseNotOK() async {
let expectation = XCTestExpectation()
var isLoading: [Bool] = []
// GIVEN
MockURLProtocol.response = .init(statusCode: 404)
// WHEN
sut.$isLoading
.collect(3)
.sink { value in
isLoading.append(contentsOf: value)
expectation.fulfill()
}
.store(in: &cancellables)
sut.filter(by: .only1Star)
await sut.fetch()
wait(for: [expectation], timeout: 2)
// THEN
XCTAssertEqual(isLoading, [false, true, false])
XCTAssertEqual(sut.filter, .only1Star)
XCTAssertFalse(sut.isFilterEnabled)
XCTAssertFalse(sut.isWordsShowing)
XCTAssertTrue(sut.items.isEmpty)
XCTAssertEqual(sut.itemsCount, 0)
XCTAssertEqual(sut.state, .error)
XCTAssertTrue(sut.words.isEmpty)
}
func testFilter_forNewOption_withSomeItems() async {
let expectation = XCTestExpectation()
var filter: [FilterOption] = []
// GIVEN
MockURLProtocol.response = .init(
statusCode: 200,
object: Feed(entries: .sample)
)
// WHEN
sut.$filter
.dropFirst()
.sink { value in
filter.append(value)
expectation.fulfill()
}
.store(in: &cancellables)
await sut.fetch()
sut.filter(by: .only1Star)
wait(for: [expectation], timeout: 2)
// THEN
XCTAssertEqual(filter, [.only1Star])
XCTAssertFalse(sut.items.isEmpty)
XCTAssertEqual(sut.items.count, 1)
XCTAssertEqual(sut.itemsCount, 2)
XCTAssertFalse(sut.words.isEmpty)
XCTAssertEqual(sut.words.count, 3)
}
func testFilter_forNewOption_withNoItems() async {
let expectation = XCTestExpectation()
var filter: [FilterOption] = []
// GIVEN
MockURLProtocol.response = .init(
statusCode: 200,
object: Feed(entries: .none)
)
// WHEN
sut.$filter
.dropFirst()
.sink { value in
filter.append(value)
expectation.fulfill()
}
.store(in: &cancellables)
await sut.fetch()
sut.filter(by: .only1Star)
wait(for: [expectation], timeout: 2)
// THEN
XCTAssertEqual(filter, [.only1Star])
XCTAssertTrue(sut.items.isEmpty)
XCTAssertEqual(sut.items.count, 0)
XCTAssertEqual(sut.itemsCount, 0)
XCTAssertTrue(sut.words.isEmpty)
XCTAssertEqual(sut.words.count, 0)
}
func testFilter_forSameOption_withSomeItems() async {
let expectation = XCTestExpectation()
var filter: [FilterOption] = []
// GIVEN
MockURLProtocol.response = .init(
statusCode: 200,
object: Feed(entries: .sample)
)
// WHEN
sut.$filter
.dropFirst()
.sink { value in
filter.append(value)
expectation.fulfill()
}
.store(in: &cancellables)
await sut.fetch()
sut.filter(by: .only1Star)
sut.filter(by: .only1Star)
wait(for: [expectation], timeout: 2)
// THEN
XCTAssertEqual(filter, [.only1Star])
XCTAssertFalse(sut.items.isEmpty)
XCTAssertEqual(sut.items.count, 1)
XCTAssertEqual(sut.itemsCount, 2)
XCTAssertFalse(sut.words.isEmpty)
XCTAssertEqual(sut.words.count, 3)
}
func testFilter_forSameOption_withNoItems() async {
let expectation = XCTestExpectation()
var filter: [FilterOption] = []
// GIVEN
MockURLProtocol.response = .init(
statusCode: 200,
object: Feed(entries: .none)
)
// WHEN
sut.$filter
.dropFirst()
.sink { value in
filter.append(value)
expectation.fulfill()
}
.store(in: &cancellables)
await sut.fetch()
sut.filter(by: .only1Star)
wait(for: [expectation], timeout: 2)
// THEN
XCTAssertEqual(filter, [.only1Star])
XCTAssertTrue(sut.items.isEmpty)
XCTAssertEqual(sut.items.count, 0)
XCTAssertEqual(sut.itemsCount, 0)
XCTAssertTrue(sut.words.isEmpty)
XCTAssertEqual(sut.words.count, 0)
}
func testItemFor_index_withSomeItems() async {
// GIVEN
MockURLProtocol.response = .init(
statusCode: 200,
object: Feed(entries: .sample)
)
// WHEN
await sut.fetch()
let item = sut.item(for: 0)
// THEN
XCTAssertNotNil(item)
}
func testItemFor_indexOutOfBounds_withSomeItems() async {
// GIVEN
MockURLProtocol.response = .init(
statusCode: 200,
object: Feed(entries: .sample)
)
// WHEN
await sut.fetch()
let item = sut.item(for: 10)
// THEN
XCTAssertNil(item)
}
func testItemFor_index_withFilteredItems() async {
// GIVEN
MockURLProtocol.response = .init(
statusCode: 200,
object: Feed(entries: .sample)
)
// WHEN
await sut.fetch()
sut.filter(by: .only1Star)
let item = sut.item(for: 1)
// THEN
XCTAssertNotNil(item)
}
func testItemFor_indexOutOfBounds_withFilteredItems() async {
// GIVEN
MockURLProtocol.response = .init(
statusCode: 200,
object: Feed(entries: .sample)
)
// WHEN
await sut.fetch()
sut.filter(by: .only1Star)
let item = sut.item(for: 2)
// THEN
XCTAssertNil(item)
}
func testItemFor_index_withNoItems() async {
// GIVEN
MockURLProtocol.response = .init(
statusCode: 200,
object: Feed(entries: .none)
)
// WHEN
await sut.fetch()
let item = sut.item(for: 0)
// THEN
XCTAssertNil(item)
}
func testOpenItemAt_index_withSomeItems() async {
// GIVEN
MockURLProtocol.response = .init(
statusCode: 200,
object: Feed(entries: .sample)
)
// WHEN
await sut.fetch()
sut.openItem(at: 0)
// THEN
XCTAssertTrue(coordination.itemOpened)
}
func testOpenItemAt_indexOutOfBounds_withSomeItems() async {
// GIVEN
MockURLProtocol.response = .init(
statusCode: 200,
object: Feed(entries: .sample)
)
// WHEN
await sut.fetch()
sut.openItem(at: 10)
// THEN
XCTAssertFalse(coordination.itemOpened)
}
func testOpenItemAt_index_withFilteredItems() async {
// GIVEN
MockURLProtocol.response = .init(
statusCode: 200,
object: Feed(entries: .sample)
)
// WHEN
sut.filter(by: .only1Star)
await sut.fetch()
sut.openItem(at: 1)
// THEN
XCTAssertTrue(coordination.itemOpened)
}
func testOpenItemAt_indexOutOfBounds_withFilteredItems() async {
// GIVEN
MockURLProtocol.response = .init(
statusCode: 200,
object: Feed(entries: .sample)
)
// WHEN
sut.filter(by: .only1Star)
await sut.fetch()
sut.openItem(at: 2)
// THEN
XCTAssertFalse(coordination.itemOpened)
}
func testOpenItemAt_index_withNoItems() async {
// GIVEN
MockURLProtocol.response = .init(
statusCode: 200,
object: Feed(entries: .none)
)
// WHEN
await sut.fetch()
sut.openItem(at: 0)
// THEN
XCTAssertFalse(coordination.itemOpened)
}
}