Skip to content

Commit

Permalink
MBL-1016: Add handling for failures in MockGraphQLClient (#1901)
Browse files Browse the repository at this point in the history
  • Loading branch information
amy-at-kickstarter committed Dec 11, 2023
1 parent 91c1afe commit 5e5cae9
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 18 deletions.
4 changes: 4 additions & 0 deletions Kickstarter.xcodeproj/project.pbxproj
Expand Up @@ -1492,6 +1492,7 @@
E10BE8E72B151D2700F73DC9 /* BlockUserInputTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10BE8E52B151CC800F73DC9 /* BlockUserInputTests.swift */; };
E10D06632ACF385E00470B5C /* FetchBackerProjectsQuery.json in Resources */ = {isa = PBXBuildFile; fileRef = E10D06622ACF385E00470B5C /* FetchBackerProjectsQuery.json */; };
E10D06652AD48C9C00470B5C /* FetchBackerProjectsQueryRequestForTests.graphql_test in Resources */ = {isa = PBXBuildFile; fileRef = E10D06642AD48C9C00470B5C /* FetchBackerProjectsQueryRequestForTests.graphql_test */; };
E170B9112B20E83B001BEDD7 /* MockGraphQLClient+CombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E170B9102B20E83B001BEDD7 /* MockGraphQLClient+CombineTests.swift */; };
E1A1491E2ACDD76800F49709 /* FetchBackerProjectsQuery.graphql in Resources */ = {isa = PBXBuildFile; fileRef = E1A1491D2ACDD76700F49709 /* FetchBackerProjectsQuery.graphql */; };
E1A149202ACDD7BF00F49709 /* FetchProjectsEnvelope+FetchBackerProjectsQueryData.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A1491F2ACDD7BF00F49709 /* FetchProjectsEnvelope+FetchBackerProjectsQueryData.swift */; };
E1A149222ACE013100F49709 /* FetchProjectsEnvelope.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A149212ACE013100F49709 /* FetchProjectsEnvelope.swift */; };
Expand Down Expand Up @@ -3068,6 +3069,7 @@
E10BE8E52B151CC800F73DC9 /* BlockUserInputTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockUserInputTests.swift; sourceTree = "<group>"; };
E10D06622ACF385E00470B5C /* FetchBackerProjectsQuery.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = FetchBackerProjectsQuery.json; sourceTree = "<group>"; };
E10D06642AD48C9C00470B5C /* FetchBackerProjectsQueryRequestForTests.graphql_test */ = {isa = PBXFileReference; lastKnownFileType = text; path = FetchBackerProjectsQueryRequestForTests.graphql_test; sourceTree = "<group>"; };
E170B9102B20E83B001BEDD7 /* MockGraphQLClient+CombineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MockGraphQLClient+CombineTests.swift"; sourceTree = "<group>"; };
E1A1491D2ACDD76700F49709 /* FetchBackerProjectsQuery.graphql */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = FetchBackerProjectsQuery.graphql; sourceTree = "<group>"; };
E1A1491F2ACDD7BF00F49709 /* FetchProjectsEnvelope+FetchBackerProjectsQueryData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "FetchProjectsEnvelope+FetchBackerProjectsQueryData.swift"; sourceTree = "<group>"; };
E1A149212ACE013100F49709 /* FetchProjectsEnvelope.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchProjectsEnvelope.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -6556,6 +6558,7 @@
8AF34C772342D6A5000B211D /* Encodable+Dictionary.swift */,
8AF34C792342D864000B211D /* Encodable+DictionaryTests.swift */,
06FE2D7526CD851300A4C0F4 /* MockGraphQLClient.swift */,
E170B9102B20E83B001BEDD7 /* MockGraphQLClient+CombineTests.swift */,
D015876A1EEB2ED6006E7684 /* NSURLSession.swift */,
);
path = extensions;
Expand Down Expand Up @@ -8716,6 +8719,7 @@
0665C74B26E930BC00A0EDA1 /* FetchProjectQueryTemplate.swift in Sources */,
06D23BBF26F2484500F76122 /* Project+FetchProjectFriendsQueryDataTests.swift in Sources */,
8AC3E0592697ABD900168BF8 /* Project.Category+CategoryFragmentTests.swift in Sources */,
E170B9112B20E83B001BEDD7 /* MockGraphQLClient+CombineTests.swift in Sources */,
D0E78C3822A981EE00AAB645 /* ClearUserUnseenActivityEnvelopeTests.swift in Sources */,
8AC3E128269F5B9700168BF8 /* UpdateBackingEnvelope+UpdateBackingMutation.DataTests.swift in Sources */,
19047FC12889B11F00BDD1A8 /* CreateSetupIntentMutationTests.swift in Sources */,
Expand Down
23 changes: 18 additions & 5 deletions KsApi/combine/CombineTestObserver.swift
Expand Up @@ -2,15 +2,28 @@ import Combine
import Foundation

public final class CombineTestObserver<Value, Error: Swift.Error> {
public private(set) var events: [Value] = []
public enum Event {
case value(Value)
case error(Error)
case finished
}

public private(set) var events: [Event] = []

private var subscriptions = Set<AnyCancellable>()

public func observe(_ publisher: any Publisher<Value, Error>) {
publisher.sink { _ in
// TODO(MBL-1017) implement this as part of writing a new test observer for Combine
fatalError("Errors haven't been handled here yet.")
publisher.sink { [weak self] completion in

switch completion {
case let .failure(error):
self?.events.append(.error(error))
case .finished:
self?.events.append(.finished)
}

} receiveValue: { [weak self] value in
self?.events.append(value)
self?.events.append(.value(value))
}
.store(in: &self.subscriptions)
}
Expand Down
59 changes: 59 additions & 0 deletions KsApi/extensions/MockGraphQLClient+CombineTests.swift
@@ -0,0 +1,59 @@
import Combine
@testable import KsApi
import XCTest

final class MockGraphQLClient_CombineTests: XCTestCase {
func testSuccess() {
let mockClient = MockGraphQLClient()
let observer = CombineTestObserver<UserEnvelope<GraphUserEmail>, ErrorEnvelope>()

let fetchGraphUserEmailQuery = GraphAPI.FetchUserEmailQuery()
let fetchUserEmailQueryData = GraphAPI.FetchUserEmailQuery
.Data(unsafeResultMap: GraphUserEnvelopeTemplates.userJSONDict)

guard let envelope = UserEnvelope<GraphUserEmail>.userEnvelope(from: fetchUserEmailQueryData) else {
XCTFail()
return
}

let publisher: AnyPublisher<UserEnvelope<GraphUserEmail>, ErrorEnvelope> =
mockClient.fetchWithResult(query: fetchGraphUserEmailQuery, result: .success(envelope))

observer.observe(publisher)

XCTAssertEqual(observer.events.count, 1)

if case let .value(observedEnvelope) = observer.events.last {
XCTAssertEqual(observedEnvelope.me, envelope.me)
} else {
XCTFail()
}
}

func testFailure() {
let mockClient = MockGraphQLClient()
let observer = CombineTestObserver<UserEnvelope<GraphUserEmail>, ErrorEnvelope>()

let fetchGraphUserEmailQuery = GraphAPI.FetchUserEmailQuery()
let error = ErrorEnvelope(
errorMessages: ["Something went wrong"],
ksrCode: .GraphQLError,
httpCode: 503,
exception: nil
)

let publisher: AnyPublisher<UserEnvelope<GraphUserEmail>, ErrorEnvelope> =
mockClient.fetchWithResult(query: fetchGraphUserEmailQuery, result: .failure(error))

observer.observe(publisher)

XCTAssertEqual(observer.events.count, 1)

if case let .error(observedError) = observer.events.last {
XCTAssertEqual(observedError.ksrCode, error.ksrCode)
XCTAssertEqual(observedError.errorMessages, error.errorMessages)
} else {
XCTFail()
}
}
}
11 changes: 3 additions & 8 deletions KsApi/extensions/MockGraphQLClient.swift
Expand Up @@ -90,14 +90,9 @@ private func producer<T, E>(for property: Result<T, E>?) -> SignalProducer<T, E>

private func producer<T, E>(for property: Result<T, E>?) -> AnyPublisher<T, E> {
switch property {
case let .success(data):
return CurrentValueSubject(data).eraseToAnyPublisher()
case .failure:
// TODO(MBL-1015) Implement this as part of further networking code updates for SwiftUI.
assertionFailure("Need to implement this behavior. I think the Fail() subject is what we want, possibly with a deferred?")
return Empty(completeImmediately: false).eraseToAnyPublisher()
case .none:
return Empty(completeImmediately: false).eraseToAnyPublisher()
case let .success(data): return CurrentValueSubject(data).eraseToAnyPublisher()
case let .failure(error): return Fail(error: error).eraseToAnyPublisher()
case .none: return Empty(completeImmediately: false).eraseToAnyPublisher()
}
}

Expand Down
32 changes: 27 additions & 5 deletions Library/ViewModels/ReportProjectFormViewModelTests.swift
Expand Up @@ -31,13 +31,21 @@ final class ReportProjectFormViewModelTests: TestCase {
userEmail.observe(vm.$retrievedEmail)

XCTAssertEqual(userEmail.events.count, 1)
XCTAssertEqual(userEmail.events.last, Optional.some(nil))

if case let .value(value) = userEmail.events.last {
XCTAssertEqual(value, nil)
}

vm.inputs.viewDidLoad()
self.scheduler.advance()

XCTAssertEqual(userEmail.events.count, 2)
XCTAssertEqual(userEmail.events.last, Optional("nativesquad@ksr.com"))

if case let .value(value) = userEmail.events.last {
XCTAssertEqual(value, "nativesquad@ksr.com")
} else {
XCTFail()
}
}
}

Expand All @@ -50,14 +58,28 @@ final class ReportProjectFormViewModelTests: TestCase {
saveButtonEnabled.observe(vm.$saveButtonEnabled)

XCTAssertEqual(saveButtonEnabled.events.count, 1)
XCTAssertEqual(saveButtonEnabled.events.last, false)

if case let .value(value) = saveButtonEnabled.events.last {
XCTAssertEqual(value, false)
} else {
XCTFail()
}

vm.detailsText = "This is my report. I don't like this project very much."
XCTAssertEqual(saveButtonEnabled.events.count, 2)
XCTAssertEqual(saveButtonEnabled.events.last, true)
if case let .value(value) = saveButtonEnabled.events.last {
XCTAssertEqual(value, true)
} else {
XCTFail()
}

vm.detailsText = ""

XCTAssertEqual(saveButtonEnabled.events.count, 3)
XCTAssertEqual(saveButtonEnabled.events.last, false)
if case let .value(value) = saveButtonEnabled.events.last {
XCTAssertEqual(value, false)
} else {
XCTFail()
}
}
}

0 comments on commit 5e5cae9

Please sign in to comment.