-
Notifications
You must be signed in to change notification settings - Fork 46
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implemented unit tests, added mocks, adjusted NetworkService and Movi…
…esSearchFlowCoordinator accordingly.
- Loading branch information
Showing
29 changed files
with
999 additions
and
145 deletions.
There are no files selected for viewing
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
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 | ||
} | ||
|
||
|
||
|
||
} |
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
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 %} |
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
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) | ||
} | ||
} | ||
} |
Oops, something went wrong.