From 5de35984dc7effb82f531a7af0f1419c0fe59adc Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Fri, 22 Mar 2024 14:09:58 +0100 Subject: [PATCH] Implemented some test cases for the "init(configuration: coordination)" and the "fetch()" functions of the FeedListViewModel view model in the Feed framework. --- .../Logic/View Models/FeedListViewModel.swift | 5 +- .../View Models/FeedListViewModelTests.swift | 264 ++++++++++++++++++ Reviews.xcodeproj/project.pbxproj | 16 +- 3 files changed, 280 insertions(+), 5 deletions(-) create mode 100644 Frameworks/Feed/Test/Tests/View Models/FeedListViewModelTests.swift diff --git a/Frameworks/Feed/Bundle/Sources/Logic/View Models/FeedListViewModel.swift b/Frameworks/Feed/Bundle/Sources/Logic/View Models/FeedListViewModel.swift index c561eac..0403702 100644 --- a/Frameworks/Feed/Bundle/Sources/Logic/View Models/FeedListViewModel.swift +++ b/Frameworks/Feed/Bundle/Sources/Logic/View Models/FeedListViewModel.swift @@ -57,7 +57,7 @@ extension FeedListViewController { var isWordsShowing: Bool { filter != .all - && !words.isEmpty + && !words.isEmpty } // MARK: Functions @@ -89,6 +89,9 @@ extension FeedListViewController { items = filter == .all ? reviewsAll : reviewsFiltered[filter] ?? [] + words = filter == .all + ? [] + : reviewsTopWords[filter] ?? [] isFilterEnabled = !items.isEmpty state = items.isEmpty diff --git a/Frameworks/Feed/Test/Tests/View Models/FeedListViewModelTests.swift b/Frameworks/Feed/Test/Tests/View Models/FeedListViewModelTests.swift new file mode 100644 index 0000000..cdf8634 --- /dev/null +++ b/Frameworks/Feed/Test/Tests/View Models/FeedListViewModelTests.swift @@ -0,0 +1,264 @@ +// +// 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: Properties + private var sut: FeedListViewController.ViewModel! + + private var cancellables: Set = [] + + // MARK: Setup + override func setUp() async throws { + sut = .init( + configuration: .init(session: .mock), + coordination: nil + ) + } + + 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() { + let expectation = XCTestExpectation() + + var isLoading: [Bool] = [] + + // GIVEN + MockURLProtocol.response = .init( + statusCode: 200, + object: Feed(entries: .sample) + ) + + // WHEN + sut.fetch() + + sut.$isLoading + .collect(3) + .sink { value in + isLoading.append(contentsOf: value) + + expectation.fulfill() + } + .store(in: &cancellables) + + 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() { + let expectation = XCTestExpectation() + + var isLoading: [Bool] = [] + + // GIVEN + MockURLProtocol.response = .init( + statusCode: 200, + object: Feed(entries: .none) + ) + + // WHEN + sut.fetch() + + sut.$isLoading + .collect(3) + .sink { value in + isLoading.append(contentsOf: value) + + expectation.fulfill() + } + .store(in: &cancellables) + + 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() { + let expectation = XCTestExpectation() + + var isLoading: [Bool] = [] + + // GIVEN + MockURLProtocol.response = .init(statusCode: 404) + + // WHEN + sut.fetch() + + sut.$isLoading + .collect(3) + .sink { value in + isLoading.append(contentsOf: value) + + expectation.fulfill() + } + .store(in: &cancellables) + + 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() { + let expectation = XCTestExpectation() + + var isLoading: [Bool] = [] + + // GIVEN + sut.filter = .only1Star + + MockURLProtocol.response = .init( + statusCode: 200, + object: Feed(entries: .sample) + ) + + // WHEN + sut.fetch() + + sut.$isLoading + .collect(3) + .sink { value in + isLoading.append(contentsOf: value) + + expectation.fulfill() + } + .store(in: &cancellables) + + wait(for: [expectation], timeout: 2) + + // THEN + XCTAssertEqual(isLoading, [false, true, false]) + 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() { + let expectation = XCTestExpectation() + + var isLoading: [Bool] = [] + + // GIVEN + sut.filter = .only1Star + + MockURLProtocol.response = .init( + statusCode: 200, + object: Feed(entries: .none) + ) + + // WHEN + sut.fetch() + + sut.$isLoading + .collect(3) + .sink { value in + isLoading.append(contentsOf: value) + + expectation.fulfill() + } + .store(in: &cancellables) + + 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) + XCTAssertTrue(sut.words.isEmpty) + } + + func testFetch_filteredItems_whenResponseNotOK() { + let expectation = XCTestExpectation() + + var isLoading: [Bool] = [] + + // GIVEN + sut.filter = .only1Star + + MockURLProtocol.response = .init(statusCode: 404) + + // WHEN + sut.fetch() + + sut.$isLoading + .collect(3) + .sink { value in + isLoading.append(contentsOf: value) + + expectation.fulfill() + } + .store(in: &cancellables) + + 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) + XCTAssertTrue(sut.words.isEmpty) + } + + func test + +} diff --git a/Reviews.xcodeproj/project.pbxproj b/Reviews.xcodeproj/project.pbxproj index a5f8d0f..4f92294 100644 --- a/Reviews.xcodeproj/project.pbxproj +++ b/Reviews.xcodeproj/project.pbxproj @@ -18,8 +18,8 @@ 02909E792BAB6B0200710E14 /* FilterOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02909E782BAB6B0200710E14 /* FilterOption.swift */; }; 02909E7B2BAB6D2E00710E14 /* Bundle+Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02909E7A2BAB6D2E00710E14 /* Bundle+Constants.swift */; }; 02909E7D2BAB7FFE00710E14 /* Review+DTOs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02909E7C2BAB7FFE00710E14 /* Review+DTOs.swift */; }; - 02B36F7C2BAD9D1A00F1A89D /* ReviewsFeedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B36F7B2BAD9D1A00F1A89D /* ReviewsFeedTests.swift */; }; 02B36F7D2BAD9D1A00F1A89D /* ReviewsFeed.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02DC7F8F2BA51793000EEEBE /* ReviewsFeed.framework */; platformFilter = ios; }; + 02B36F852BAD9DDB00F1A89D /* FeedListViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B36F842BAD9DDB00F1A89D /* FeedListViewModelTests.swift */; }; 02B36F892BADB26C00F1A89D /* Array+ReviewDTOs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B36F882BADB26C00F1A89D /* Array+ReviewDTOs.swift */; }; 02C1B1972BAC9BFE001781DE /* FeedListCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02C1B1962BAC9BFE001781DE /* FeedListCoordinator.swift */; }; 02C1B1A92BACA722001781DE /* AppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02C1B1A82BACA722001781DE /* AppCoordinator.swift */; }; @@ -86,7 +86,7 @@ 02909E7A2BAB6D2E00710E14 /* Bundle+Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+Constants.swift"; sourceTree = ""; }; 02909E7C2BAB7FFE00710E14 /* Review+DTOs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Review+DTOs.swift"; sourceTree = ""; }; 02B36F792BAD9D1A00F1A89D /* FeedTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FeedTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 02B36F7B2BAD9D1A00F1A89D /* ReviewsFeedTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewsFeedTests.swift; sourceTree = ""; }; + 02B36F842BAD9DDB00F1A89D /* FeedListViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedListViewModelTests.swift; sourceTree = ""; }; 02B36F882BADB26C00F1A89D /* Array+ReviewDTOs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+ReviewDTOs.swift"; sourceTree = ""; }; 02C1B1962BAC9BFE001781DE /* FeedListCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedListCoordinator.swift; sourceTree = ""; }; 02C1B1A82BACA722001781DE /* AppCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinator.swift; sourceTree = ""; }; @@ -254,11 +254,19 @@ 02B36F742BAD9C4500F1A89D /* Tests */ = { isa = PBXGroup; children = ( - 02B36F7B2BAD9D1A00F1A89D /* ReviewsFeedTests.swift */, + 02B36F832BAD9DC700F1A89D /* View Models */, ); path = Tests; sourceTree = ""; }; + 02B36F832BAD9DC700F1A89D /* View Models */ = { + isa = PBXGroup; + children = ( + 02B36F842BAD9DDB00F1A89D /* FeedListViewModelTests.swift */, + ); + path = "View Models"; + sourceTree = ""; + }; 02B36F862BADB1FD00F1A89D /* Previews */ = { isa = PBXGroup; children = ( @@ -574,7 +582,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 02B36F7C2BAD9D1A00F1A89D /* ReviewsFeedTests.swift in Sources */, + 02B36F852BAD9DDB00F1A89D /* FeedListViewModelTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; };