Skip to content

Commit

Permalink
Implemented unit tests, added mocks, adjusted NetworkService and Movi…
Browse files Browse the repository at this point in the history
…esSearchFlowCoordinator accordingly.
  • Loading branch information
sgl0v committed Apr 11, 2021
1 parent 695263b commit 5fc1aec
Show file tree
Hide file tree
Showing 29 changed files with 999 additions and 145 deletions.
234 changes: 234 additions & 0 deletions Mocks/AutoMockable.generated.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
// Generated using Sourcery 1.0.0 — https://github.com/krzysztofzablocki/Sourcery
// DO NOT EDIT

// swiftlint:disable line_length
// swiftlint:disable variable_name

import Foundation
import XCTest
import Combine
@testable import TMDB














class ApplicationFlowCoordinatorDependencyProviderMock: ApplicationFlowCoordinatorDependencyProvider {

//MARK: - moviesNavigationViewController

var moviesNavigationViewControllerNavigatorCallsCount = 0
var moviesNavigationViewControllerNavigatorCalled: Bool {
return moviesNavigationViewControllerNavigatorCallsCount > 0
}
var moviesNavigationViewControllerNavigatorReceivedNavigator: MoviesNavigator?
var moviesNavigationViewControllerNavigatorReceivedInvocations: [MoviesNavigator] = []
var moviesNavigationViewControllerNavigatorReturnValue: UINavigationController!
var moviesNavigationViewControllerNavigatorClosure: ((MoviesNavigator) -> UINavigationController)?

func moviesNavigationViewController(navigator: MoviesNavigator) -> UINavigationController {
moviesNavigationViewControllerNavigatorCallsCount += 1
moviesNavigationViewControllerNavigatorReceivedNavigator = navigator
moviesNavigationViewControllerNavigatorReceivedInvocations.append(navigator)
return moviesNavigationViewControllerNavigatorClosure.map({ $0(navigator) }) ?? moviesNavigationViewControllerNavigatorReturnValue
}

//MARK: - movieDetailsController

var movieDetailsControllerCallsCount = 0
var movieDetailsControllerCalled: Bool {
return movieDetailsControllerCallsCount > 0
}
var movieDetailsControllerReceivedMovieId: Int?
var movieDetailsControllerReceivedInvocations: [Int] = []
var movieDetailsControllerReturnValue: UIViewController!
var movieDetailsControllerClosure: ((Int) -> UIViewController)?

func movieDetailsController(_ movieId: Int) -> UIViewController {
movieDetailsControllerCallsCount += 1
movieDetailsControllerReceivedMovieId = movieId
movieDetailsControllerReceivedInvocations.append(movieId)
return movieDetailsControllerClosure.map({ $0(movieId) }) ?? movieDetailsControllerReturnValue
}



}
class CoreDataServiceTypeMock: CoreDataServiceType {

//MARK: - fetchAll

var fetchAllCallsCount = 0
var fetchAllCalled: Bool {
return fetchAllCallsCount > 0
}
var fetchAllReturnValue: AnyPublisher<[Movie], Error>!
var fetchAllClosure: (() -> AnyPublisher<[Movie], Error>)?

func fetchAll() -> AnyPublisher<[Movie], Error> {
fetchAllCallsCount += 1
return fetchAllClosure.map({ $0() }) ?? fetchAllReturnValue
}

//MARK: - fetch

var fetchWithCallsCount = 0
var fetchWithCalled: Bool {
return fetchWithCallsCount > 0
}
var fetchWithReceivedId: Int?
var fetchWithReceivedInvocations: [Int] = []
var fetchWithReturnValue: AnyPublisher<Movie, Error>!
var fetchWithClosure: ((Int) -> AnyPublisher<Movie, Error>)?

func fetch(with id: Int) -> AnyPublisher<Movie, Error> {
fetchWithCallsCount += 1
fetchWithReceivedId = id
fetchWithReceivedInvocations.append(id)
return fetchWithClosure.map({ $0(id) }) ?? fetchWithReturnValue
}

//MARK: - add

var addMoviesCallsCount = 0
var addMoviesCalled: Bool {
return addMoviesCallsCount > 0
}
var addMoviesReceivedMovies: [Movie]?
var addMoviesReceivedInvocations: [[Movie]] = []
var addMoviesReturnValue: AnyPublisher<[Movie], Error>!
var addMoviesClosure: (([Movie]) -> AnyPublisher<[Movie], Error>)?

func add(movies: [Movie]) -> AnyPublisher<[Movie], Error> {
addMoviesCallsCount += 1
addMoviesReceivedMovies = movies
addMoviesReceivedInvocations.append(movies)
return addMoviesClosure.map({ $0(movies) }) ?? addMoviesReturnValue
}

//MARK: - deleteAll

var deleteAllCallsCount = 0
var deleteAllCalled: Bool {
return deleteAllCallsCount > 0
}
var deleteAllReturnValue: AnyPublisher<Void, Error>!
var deleteAllClosure: (() -> AnyPublisher<Void, Error>)?

func deleteAll() -> AnyPublisher<Void, Error> {
deleteAllCallsCount += 1
return deleteAllClosure.map({ $0() }) ?? deleteAllReturnValue
}



}
class ImageLoaderServiceTypeMock: ImageLoaderServiceType {

//MARK: - loadImage

var loadImageFromCallsCount = 0
var loadImageFromCalled: Bool {
return loadImageFromCallsCount > 0
}
var loadImageFromReceivedUrl: URL?
var loadImageFromReceivedInvocations: [URL] = []
var loadImageFromReturnValue: AnyPublisher<UIImage?, Never>!
var loadImageFromClosure: ((URL) -> AnyPublisher<UIImage?, Never>)?

func loadImage(from url: URL) -> AnyPublisher<UIImage?, Never> {
loadImageFromCallsCount += 1
loadImageFromReceivedUrl = url
loadImageFromReceivedInvocations.append(url)
return loadImageFromClosure.map({ $0(url) }) ?? loadImageFromReturnValue
}



}
class MoviesNavigatorMock: MoviesNavigator {

//MARK: - showDetails

var showDetailsForMovieCallsCount = 0
var showDetailsForMovieCalled: Bool {
return showDetailsForMovieCallsCount > 0
}
var showDetailsForMovieReceivedMovieId: Int?
var showDetailsForMovieReceivedInvocations: [Int] = []
var showDetailsForMovieClosure: ((Int) -> Void)?

func showDetails(forMovie movieId: Int) {
showDetailsForMovieCallsCount += 1
showDetailsForMovieReceivedMovieId = movieId
showDetailsForMovieReceivedInvocations.append(movieId)
showDetailsForMovieClosure?(movieId)
}



}
class MoviesUseCaseTypeMock: MoviesUseCaseType {

//MARK: - loadMovies

var loadMoviesCallsCount = 0
var loadMoviesCalled: Bool {
return loadMoviesCallsCount > 0
}
var loadMoviesReturnValue: AnyPublisher<Result<[Movie], Error>, Never>!
var loadMoviesClosure: (() -> AnyPublisher<Result<[Movie], Error>, Never>)?

func loadMovies() -> AnyPublisher<Result<[Movie], Error>, Never> {
loadMoviesCallsCount += 1
return loadMoviesClosure.map({ $0() }) ?? loadMoviesReturnValue
}

//MARK: - movieDetails

var movieDetailsWithCallsCount = 0
var movieDetailsWithCalled: Bool {
return movieDetailsWithCallsCount > 0
}
var movieDetailsWithReceivedId: Int?
var movieDetailsWithReceivedInvocations: [Int] = []
var movieDetailsWithReturnValue: AnyPublisher<Result<Movie, Error>, Never>!
var movieDetailsWithClosure: ((Int) -> AnyPublisher<Result<Movie, Error>, Never>)?

func movieDetails(with id: Int) -> AnyPublisher<Result<Movie, Error>, Never> {
movieDetailsWithCallsCount += 1
movieDetailsWithReceivedId = id
movieDetailsWithReceivedInvocations.append(id)
return movieDetailsWithClosure.map({ $0(id) }) ?? movieDetailsWithReturnValue
}

//MARK: - loadImage

var loadImageForSizeCallsCount = 0
var loadImageForSizeCalled: Bool {
return loadImageForSizeCallsCount > 0
}
var loadImageForSizeReceivedArguments: (movie: Movie, size: ImageSize)?
var loadImageForSizeReceivedInvocations: [(movie: Movie, size: ImageSize)] = []
var loadImageForSizeReturnValue: AnyPublisher<UIImage?, Never>!
var loadImageForSizeClosure: ((Movie, ImageSize) -> AnyPublisher<UIImage?, Never>)?

func loadImage(for movie: Movie, size: ImageSize) -> AnyPublisher<UIImage?, Never> {
loadImageForSizeCallsCount += 1
loadImageForSizeReceivedArguments = (movie: movie, size: size)
loadImageForSizeReceivedInvocations.append((movie: movie, size: size))
return loadImageForSizeClosure.map({ $0(movie, size) }) ?? loadImageForSizeReturnValue
}



}
126 changes: 126 additions & 0 deletions Mocks/AutoMockable.stencil
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// swiftlint:disable line_length
// swiftlint:disable variable_name

import Foundation
import XCTest
import Combine
@testable import TMDB

{% macro swiftifyMethodName name %}{{ name | replace:"(","_" | replace:")","" | replace:":","_" | replace:"`","" | snakeToCamelCase | lowerFirstWord }}{% endmacro %}

{% macro methodThrowableErrorDeclaration method %}
var {% call swiftifyMethodName method.selectorName %}ThrowableError: Error?
{% endmacro %}

{% macro methodThrowableErrorUsage method %}
if let error = {% call swiftifyMethodName method.selectorName %}ThrowableError {
throw error
}
{% endmacro %}

{% macro methodReceivedParameters method %}
{%if method.parameters.count == 1 %}
{% call swiftifyMethodName method.selectorName %}Received{% for param in method.parameters %}{{ param.name|upperFirstLetter }} = {{ param.name }}{% endfor %}
{% call swiftifyMethodName method.selectorName %}ReceivedInvocations.append({% for param in method.parameters %}{{ param.name }}){% endfor %}
{% else %}
{% if not method.parameters.count == 0 %}
{% call swiftifyMethodName method.selectorName %}ReceivedArguments = ({% for param in method.parameters %}{{ param.name }}: {{ param.name }}{% if not forloop.last%}, {% endif %}{% endfor %})
{% call swiftifyMethodName method.selectorName %}ReceivedInvocations.append(({% for param in method.parameters %}{{ param.name }}: {{ param.name }}{% if not forloop.last%}, {% endif %}{% endfor %}))
{% endif %}
{% endif %}
{% endmacro %}

{% macro methodClosureName method %}{% call swiftifyMethodName method.selectorName %}Closure{% endmacro %}

{% macro closureReturnTypeName method %}{% if method.isOptionalReturnType %}{{ method.unwrappedReturnTypeName }}?{% else %}{{ method.returnTypeName }}{% endif %}{% endmacro %}

{% macro methodClosureDeclaration method %}
var {% call methodClosureName method %}: (({% for param in method.parameters %}{{ param.typeName }}{% if not forloop.last %}, {% endif %}{% endfor %}) {% if method.throws %}throws {% endif %}-> {% if method.isInitializer %}Void{% else %}{% call closureReturnTypeName method %}{% endif %})?
{% endmacro %}

{% macro methodClosureCallParameters method %}{% for param in method.parameters %}{{ param.name }}{% if not forloop.last %}, {% endif %}{% endfor %}{% endmacro %}

{% macro mockMethod method %}
//MARK: - {{ method.shortName }}

{% if method.throws %}
{% call methodThrowableErrorDeclaration method %}
{% endif %}
{% if not method.isInitializer %}
var {% call swiftifyMethodName method.selectorName %}CallsCount = 0
var {% call swiftifyMethodName method.selectorName %}Called: Bool {
return {% call swiftifyMethodName method.selectorName %}CallsCount > 0
}
{% endif %}
{% if method.parameters.count == 1 %}
var {% call swiftifyMethodName method.selectorName %}Received{% for param in method.parameters %}{{ param.name|upperFirstLetter }}: {{ '(' if param.isClosure }}{{ param.typeName.unwrappedTypeName }}{{ ')' if param.isClosure }}?{% endfor %}
var {% call swiftifyMethodName method.selectorName %}ReceivedInvocations{% for param in method.parameters %}: [{{ '(' if param.isClosure }}{{ param.typeName.unwrappedTypeName }}{{ ')' if param.isClosure }}{%if param.typeName.isOptional%}?{%endif%}]{% endfor %} = []
{% elif not method.parameters.count == 0 %}
var {% call swiftifyMethodName method.selectorName %}ReceivedArguments: ({% for param in method.parameters %}{{ param.name }}: {{ param.unwrappedTypeName if param.typeAttributes.escaping else param.typeName }}{{ ', ' if not forloop.last }}{% endfor %})?
var {% call swiftifyMethodName method.selectorName %}ReceivedInvocations: [({% for param in method.parameters %}{{ param.name }}: {{ param.unwrappedTypeName if param.typeAttributes.escaping else param.typeName }}{{ ', ' if not forloop.last }}{% endfor %})] = []
{% endif %}
{% if not method.returnTypeName.isVoid and not method.isInitializer %}
var {% call swiftifyMethodName method.selectorName %}ReturnValue: {{ method.returnTypeName }}{{ '!' if not method.isOptionalReturnType }}
{% endif %}
{% call methodClosureDeclaration method %}

{% if method.isInitializer %}
required {{ method.name }} {
{% call methodReceivedParameters method %}
{% call methodClosureName method %}?({% call methodClosureCallParameters method %})
}
{% else %}
func {{ method.name }}{{ ' throws' if method.throws }}{% if not method.returnTypeName.isVoid %} -> {{ method.returnTypeName }}{% endif %} {
{% if method.throws %}
{% call methodThrowableErrorUsage method %}
{% endif %}
{% call swiftifyMethodName method.selectorName %}CallsCount += 1
{% call methodReceivedParameters method %}
{% if method.returnTypeName.isVoid %}
{% if method.throws %}try {% endif %}{% call methodClosureName method %}?({% call methodClosureCallParameters method %})
{% else %}
return {{ 'try ' if method.throws }}{% call methodClosureName method %}.map({ {{ 'try ' if method.throws }}$0({% call methodClosureCallParameters method %}) }) ?? {% call swiftifyMethodName method.selectorName %}ReturnValue
{% endif %}
}

{% endif %}
{% endmacro %}

{% macro mockOptionalVariable variable %}
var {% call mockedVariableName variable %}: {{ variable.typeName }}
{% endmacro %}

{% macro mockNonOptionalArrayOrDictionaryVariable variable %}
var {% call mockedVariableName variable %}: {{ variable.typeName }} = {% if variable.isArray %}[]{% elif variable.isDictionary %}[:]{% endif %}
{% endmacro %}

{% macro mockNonOptionalVariable variable %}
var {% call mockedVariableName variable %}: {{ variable.typeName }} {
get { return {% call underlyingMockedVariableName variable %} }
set(value) { {% call underlyingMockedVariableName variable %} = value }
}
var {% call underlyingMockedVariableName variable %}: {{ variable.typeName }}!
{% endmacro %}

{% macro underlyingMockedVariableName variable %}underlying{{ variable.name|upperFirstLetter }}{% endmacro %}
{% macro mockedVariableName variable %}{{ variable.name }}{% endmacro %}

{% for type in types.protocols where type.based.AutoMockable or type|annotated:"AutoMockable" %}{% if type.name != "AutoMockable" %}
class {{ type.name }}Mock: {{ type.name }} {
{% for variable in type.allVariables|!definedInExtension %}
{% if variable.isOptional %}{% call mockOptionalVariable variable %}{% elif variable.isArray or variable.isDictionary %}{% call mockNonOptionalArrayOrDictionaryVariable variable %}{% else %}{% call mockNonOptionalVariable variable %}{% endif %}
{% endfor %}

{% for method in type.allMethods|!definedInExtension %}
{% call mockMethod method %}
{% endfor %}

{% if type.name|hasSuffix:"ManagerProtocol" %}
//MARK: - Register manager
func register() {
ServiceRegistry.register({{ type.name }}.self, factory: { return self }, reset: {})
}
{% endif %}

}
{% endif %}{% endfor %}
25 changes: 25 additions & 0 deletions Mocks/NetworkServiceTypeMock.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// TMDB
//
// Created by Maksym Shcheglov.
// Copyright © 2021 Maksym Shcheglov. All rights reserved.
//

import Foundation
import Combine
@testable import TMDB

class NetworkServiceTypeMock: NetworkServiceType {

var responses = [String:Any]()

func load<T>(_ resource: Resource<T>) -> AnyPublisher<T, Error> where T : Decodable {
if let response = responses[resource.url.path] as? T {
return .just(response)
} else if let error = responses[resource.url.path] as? NetworkError {
return .fail(error)
} else {
return .fail(NetworkError.invalidRequest)
}
}
}
Loading

0 comments on commit 5fc1aec

Please sign in to comment.