Skip to content

Commit

Permalink
Merge pull request #6 from popei69/develop
Browse files Browse the repository at this point in the history
Update unit tests for mocked data
  • Loading branch information
popei69 authored Feb 6, 2019
2 parents 699951d + 2567021 commit 6cdbe6e
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 57 deletions.
8 changes: 6 additions & 2 deletions TemplateProject.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@
14008A9E20193EB800F84447 /* CurrencyDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14008A9D20193EB800F84447 /* CurrencyDataSource.swift */; };
141895B82023B4BC0029E238 /* CurrencyDataSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 141895B72023B4BC0029E238 /* CurrencyDataSourceTests.swift */; };
141895BA2023BBF70029E238 /* CurrencyServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 141895B92023BBF70029E238 /* CurrencyServiceTests.swift */; };
141895BC2023C82B0029E238 /* sample.json in Resources */ = {isa = PBXBuildFile; fileRef = 141895BB2023C82B0029E238 /* sample.json */; };
141895BE2023C8770029E238 /* CurrencyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 141895BD2023C8770029E238 /* CurrencyTests.swift */; };
142350DC2009668500930F1D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 142350DB2009668500930F1D /* AppDelegate.swift */; };
142350DE2009668500930F1D /* CurrencyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 142350DD2009668500930F1D /* CurrencyViewController.swift */; };
142350E12009668500930F1D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 142350DF2009668500930F1D /* Main.storyboard */; };
142350E32009668500930F1D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 142350E22009668500930F1D /* Assets.xcassets */; };
142350E62009668500930F1D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 142350E42009668500930F1D /* LaunchScreen.storyboard */; };
1456E4B5200A7F2200DCF2AF /* DynamicValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1456E4B4200A7F2200DCF2AF /* DynamicValue.swift */; };
148CF5E6220A8EEA001DE84B /* FileDataService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 148CF5E5220A8EEA001DE84B /* FileDataService.swift */; };
148CF5E7220A8F0B001DE84B /* sample.json in Resources */ = {isa = PBXBuildFile; fileRef = 141895BB2023C82B0029E238 /* sample.json */; };
14917E0C200A489A00234393 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14917E0B200A489A00234393 /* Result.swift */; };
14917E0E200A48AB00234393 /* ErrorResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14917E0D200A48AB00234393 /* ErrorResult.swift */; };
14917E11200A491C00234393 /* RequestFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14917E10200A491C00234393 /* RequestFactory.swift */; };
Expand Down Expand Up @@ -59,6 +60,7 @@
142350EC2009668500930F1D /* TemplateProjectTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TemplateProjectTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
142350F22009668500930F1D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
1456E4B4200A7F2200DCF2AF /* DynamicValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicValue.swift; sourceTree = "<group>"; };
148CF5E5220A8EEA001DE84B /* FileDataService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FileDataService.swift; path = ../../../../../Desktop/FileDataService.swift; sourceTree = "<group>"; };
14917E0B200A489A00234393 /* Result.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = "<group>"; };
14917E0D200A48AB00234393 /* ErrorResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorResult.swift; sourceTree = "<group>"; };
14917E10200A491C00234393 /* RequestFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestFactory.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -165,6 +167,7 @@
14917E0F200A490400234393 /* Network */,
14917E0A200A488D00234393 /* Result */,
14917E24200A537600234393 /* CurrencyService.swift */,
148CF5E5220A8EEA001DE84B /* FileDataService.swift */,
);
path = Service;
sourceTree = "<group>";
Expand Down Expand Up @@ -298,6 +301,7 @@
buildActionMask = 2147483647;
files = (
142350E62009668500930F1D /* LaunchScreen.storyboard in Resources */,
148CF5E7220A8F0B001DE84B /* sample.json in Resources */,
142350E32009668500930F1D /* Assets.xcassets in Resources */,
142350E12009668500930F1D /* Main.storyboard in Resources */,
);
Expand All @@ -307,7 +311,6 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
141895BC2023C82B0029E238 /* sample.json in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -318,6 +321,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
148CF5E6220A8EEA001DE84B /* FileDataService.swift in Sources */,
14917E19200A508700234393 /* CurrencyCell.swift in Sources */,
14917E14200A4ECD00234393 /* Currency.swift in Sources */,
14917E23200A530800234393 /* ParserHelper.swift in Sources */,
Expand Down
6 changes: 4 additions & 2 deletions TemplateProject/Model/DynamicValue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@

import Foundation

typealias CompletionHandler = (() -> Void)

class DynamicValue<T> {

typealias CompletionHandler = ((T) -> Void)

var value : T {
didSet {
self.notify()
Expand All @@ -33,7 +35,7 @@ class DynamicValue<T> {
}

private func notify() {
observers.forEach({ $0.value() })
observers.forEach({ $0.value(value) })
}

deinit {
Expand Down
42 changes: 42 additions & 0 deletions TemplateProject/Service/FileDataService.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//
// File.swift
// TemplateProject
//
// Created by Benoit PASQUIER on 02/02/2019.
// Copyright © 2019 Benoit PASQUIER. All rights reserved.
//

import Foundation

final class FileDataService : CurrencyServiceProtocol {

func fetchConverter(_ completion: @escaping ((Result<Converter, ErrorResult>) -> Void)) {

// giving a sample json file
guard let data = FileManager.readJson(forResource: "sample") else {
completion(Result.failure(ErrorResult.custom(string: "No file or data")))
return
}

ParserHelper.parse(data: data, completion: completion)
}
}


extension FileManager {

static func readJson(forResource fileName: String ) -> Data? {

let bundle = Bundle(for: FileDataService.self)
if let path = bundle.path(forResource: fileName, ofType: "json") {
do {
let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
return data
} catch {
// handle error
}
}

return nil
}
}
10 changes: 9 additions & 1 deletion TemplateProject/View/CurrencyViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,18 @@ class CurrencyViewController: UIViewController {
self.title = "£ Exchange rate"

self.tableView.dataSource = self.dataSource
self.dataSource.data.addAndNotify(observer: self) { [weak self] in
self.dataSource.data.addAndNotify(observer: self) { [weak self] _ in
self?.tableView.reloadData()
}

// add error handling example
self.viewModel.onErrorHandling = { [weak self] error in
// display error ?
let controller = UIAlertController(title: "An error occured", message: "Oops, something went wrong!", preferredStyle: .alert)
controller.addAction(UIAlertAction(title: "Close", style: .cancel, handler: nil))
self?.present(controller, animated: true, completion: nil)
}

self.viewModel.fetchCurrencies()
}
}
18 changes: 6 additions & 12 deletions TemplateProject/ViewModel/CurrencyViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,33 +13,27 @@ struct CurrencyViewModel {
weak var dataSource : GenericDataSource<CurrencyRate>?
weak var service: CurrencyServiceProtocol?

init(service: CurrencyServiceProtocol = CurrencyService.shared, dataSource : GenericDataSource<CurrencyRate>?) {
var onErrorHandling : ((ErrorResult?) -> Void)?

init(service: CurrencyServiceProtocol = FileDataService.shared, dataSource : GenericDataSource<CurrencyRate>?) {
self.dataSource = dataSource
self.service = service
}

func fetchCurrencies(_ completion: ((Result<Bool, ErrorResult>) -> Void)? = nil) {
func fetchCurrencies() {

guard let service = service else {
completion?(Result.failure(ErrorResult.custom(string: "Missing service")))
onErrorHandling?(ErrorResult.custom(string: "Missing service"))
return
}

service.fetchConverter { result in

DispatchQueue.main.async {
switch result {
case .success(let converter) :
// reload data
self.dataSource?.data.value = converter.rates
completion?(Result.success(true))

break
case .failure(let error) :
print("Parser error \(error)")
completion?(Result.failure(error))

break
self.onErrorHandling?(error)
}
}
}
Expand Down
18 changes: 0 additions & 18 deletions TemplateProjectTests/CurrencyTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,21 +71,3 @@ class CurrencyTests: XCTestCase {
}
}
}

extension FileManager {

static func readJson(forResource fileName: String ) -> Data? {

let bundle = Bundle(for: CurrencyTests.self)
if let path = bundle.path(forResource: fileName, ofType: "json") {
do {
let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
return data
} catch {
// handle error
}
}

return nil
}
}
47 changes: 25 additions & 22 deletions TemplateProjectTests/CurrencyViewModelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,50 +31,53 @@ class CurrencyViewModelTests: XCTestCase {

func testFetchWithNoService() {

let expectation = XCTestExpectation(description: "No service currency")

// giving no service to a view model
viewModel.service = nil

// expected to not be able to fetch currencies
viewModel.fetchCurrencies { result in
switch result {
case .success(_) :
XCTAssert(false, "ViewModel should not be able to fetch without service")
default:
break
}
viewModel.onErrorHandling = { error in
expectation.fulfill()
}

viewModel.fetchCurrencies()
wait(for: [expectation], timeout: 5.0)
}

func testFetchCurrencies() {

let expectation = XCTestExpectation(description: "Currency fetch")

// giving a service mocking currencies
service.converter = Converter(base: "GBP", date: "01-01-2018", rates: [])

// expected completion to succeed
viewModel.fetchCurrencies { result in
switch result {
case .failure(_) :
XCTAssert(false, "ViewModel should not be able to fetch without service")
default:
break
}
viewModel.onErrorHandling = { _ in
XCTAssert(false, "ViewModel should not be able to fetch without service")
}

dataSource.data.addObserver(self) { _ in
expectation.fulfill()
}

viewModel.fetchCurrencies()
wait(for: [expectation], timeout: 5.0)
}

func testFetchNoCurrencies() {

let expectation = XCTestExpectation(description: "No currency")

// giving a service mocking error during fetching currencies
service.converter = nil

// expected completion to fail
viewModel.fetchCurrencies { result in
switch result {
case .success(_) :
XCTAssert(false, "ViewModel should not be able to fetch ")
default:
break
}
viewModel.onErrorHandling = { error in
expectation.fulfill()
}

viewModel.fetchCurrencies()
wait(for: [expectation], timeout: 5.0)
}
}

Expand Down

0 comments on commit 6cdbe6e

Please sign in to comment.