Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Test Mock Custom Scalar conversion #2584

Merged
merged 3 commits into from
Oct 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 49 additions & 15 deletions Sources/ApolloTestSupport/TestMock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@
import Foundation

@dynamicMemberLookup
public class Mock<O: MockObject>: AnyMock, JSONEncodable, Hashable {
public class Mock<O: MockObject>: AnyMock, Hashable {

public var _data: JSONEncodableDictionary
public var _data: [String: AnyHashable]

public init() {
_data = ["__typename": O.objectType.typename]
}

public var __typename: String { _data["__typename"] as! String }

public subscript<T: AnyScalarType>(dynamicMember keyPath: KeyPath<O.MockFields, Field<T>>) -> T? {
public subscript<T: AnyScalarType & Hashable>(dynamicMember keyPath: KeyPath<O.MockFields, Field<T>>) -> T? {
get {
let field = O._mockFields[keyPath: keyPath]
return _data[field.key.description] as? T
Expand All @@ -34,41 +34,58 @@ public class Mock<O: MockObject>: AnyMock, JSONEncodable, Hashable {
}
set {
let field = O._mockFields[keyPath: keyPath]
_data[field.key.description] = (newValue as? (any JSONEncodable))
_data[field.key.description] = (newValue as? AnyHashable)
}
}

// MARK: JSONEncodable
public subscript<T: MockFieldValue>(
dynamicMember keyPath: KeyPath<O.MockFields, Field<Array<T>>>
) -> [T.MockValueCollectionType.Element]? {
get {
let field = O._mockFields[keyPath: keyPath]
return _data[field.key.description] as? [T.MockValueCollectionType.Element]
}
set {
let field = O._mockFields[keyPath: keyPath]
_data[field.key.description] = newValue?._unsafelyConvertToMockValue()
}
}

public var _jsonObject: JSONObject { _data._jsonObject }
public var _jsonValue: JSONValue { _jsonObject }
public var _selectionSetMockData: JSONObject {
_data.mapValues {
if let mock = $0 as? AnyMock {
return mock._selectionSetMockData
}
return $0
}
}

// MARK: Hashable

public static func ==(lhs: Mock<O>, rhs: Mock<O>) -> Bool {
NSDictionary(dictionary: lhs._data).isEqual(to: rhs._data)
lhs._data == rhs._data
}

public func hash(into hasher: inout Hasher) {
hasher.combine(_data._jsonValue)
hasher.combine(_data)
}
}

// MARK: - Selection Set Conversion

public extension SelectionSet {
static func from(
_ mock: any AnyMock,
_ mock: AnyMock,
withVariables variables: GraphQLOperation.Variables? = nil
) -> Self {
Self.init(data: DataDict(mock._jsonObject, variables: variables))
Self.init(data: DataDict(mock._selectionSetMockData, variables: variables))
}
}

// MARK: - Helper Protocols

public protocol AnyMock: JSONEncodable {
var _jsonObject: JSONObject { get }
public protocol AnyMock {
var _selectionSetMockData: JSONObject { get }
}

public protocol MockObject: MockFieldValue {
Expand All @@ -91,10 +108,27 @@ extension Union: MockFieldValue {
public typealias MockValueCollectionType = Array<AnyMock>
}

extension Optional: MockFieldValue where Wrapped: MockFieldValue {
public typealias MockValueCollectionType = Array<Optional<Wrapped.MockValueCollectionType.Element>>
}

extension Array: MockFieldValue where Array.Element: MockFieldValue {
public typealias MockValueCollectionType = Array<Element.MockValueCollectionType>
}

extension Optional: MockFieldValue where Wrapped: MockFieldValue {
public typealias MockValueCollectionType = Array<Optional<Wrapped.MockValueCollectionType.Element>>
fileprivate extension Array {
func _unsafelyConvertToMockValue() -> [AnyHashable?] {
map { element in
switch element {
case let element as AnyHashable:
return element

case let innerArray as Array<Any>:
return innerArray._unsafelyConvertToMockValue()

default:
return nil
}
}
}
}
69 changes: 64 additions & 5 deletions Tests/ApolloTests/TestMockTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,18 @@ class TestMockTests: XCTestCase {
expect(mock.listOfOptionalObjects).to(equal([cat1, nil, cat2, nil]))
}

func test__mock__setEnumField__fieldIsSet() throws {
// given
let mock = Mock<Dog>()

// when
mock.speciesType = .case(.canine)

// then
expect(mock._data["speciesType"] as? GraphQLEnum<Species>).to(equal(.case(.canine)))
expect(mock.speciesType).to(equal(.case(.canine)))
}

func test__mock__setInterfaceField__fieldIsSet() throws {
// given
let mock = Mock<Dog>()
Expand Down Expand Up @@ -208,9 +220,9 @@ class TestMockTests: XCTestCase {
expect(expected.isEqual(mock.listOfOptionalInterfaces as [AnyMock?]?)).to(beTrue())
}

// MARK: JSONEncodable Tests
// MARK: SelectionSet Mock Data Conversion Tests

func test__jsonValue__givenObjectFieldSetToOtherObject__convertsObjectToJSONDict() throws {
func test___selectionSetMockData__givenObjectFieldSetToOtherObject__convertsObjectToDict() throws {
// given
let mock = Mock<Dog>()
let height = Mock<Height>()
Expand All @@ -221,7 +233,7 @@ class TestMockTests: XCTestCase {
mock.height = height
mock.height?.yards = 3

let actual = mock._jsonObject
let actual = mock._selectionSetMockData
let heightDict = actual["height"] as? JSONObject

// then
Expand All @@ -231,21 +243,46 @@ class TestMockTests: XCTestCase {
expect(heightDict?["yards"] as? Int).to(equal(3))
}

func test___selectionSetMockData__givenCustomScalarField__convertsObjectToDictWithCustomScalarIntact() throws {
// given
let mock = Mock<Dog>()
let customScalar = MockCustomScalar(value: 12)

// when
mock.customScalar = customScalar

let actual = mock._selectionSetMockData
let actualCustomScalar = actual["customScalar"] as? MockCustomScalar
// then
expect(actualCustomScalar?.value).to(equal(12))
}

// MARK: Hashable Tests

func test__hashable__mockIsHashableByData() throws {
// given
let mock1 = Mock<Dog>()
let mock2 = Mock<Dog>()
let mock3 = Mock<Dog>()
let mock4 = Mock<Dog>()

mock1.id = "1"
mock1.listOfOptionalInterfaces = [nil, Mock<Cat>()]

mock2.id = "2"
mock2.listOfOptionalInterfaces = [nil, Mock<Cat>()]

mock3.id = "1"
mock3.listOfOptionalInterfaces = [nil, Mock<Dog>()]

mock4.id = "1"
mock4.listOfOptionalInterfaces = [nil, Mock<Cat>()]

// when
let mocks = Set([mock1, mock2])
let mocks = Set([mock1, mock2, mock3, mock4])

// then
expect(mocks).to(equal(Set([mock1, mock2])))
expect(mocks).to(equal(Set([mock1, mock2, mock3])))
}

}
Expand Down Expand Up @@ -294,6 +331,8 @@ class Dog: MockObject {
struct MockFields {
@Field<String>("id") public var id
@Field<String>("species") public var species
@Field<GraphQLEnum<Species>>("speciesType") public var speciesType
@Field<MockCustomScalar>("customScalar") public var customScalar
@Field<Height>("height") public var height
@Field<[String]>("listOfStrings") public var listOfStrings
@Field<Animal>("bestFriend") public var bestFriend
Expand All @@ -317,6 +356,7 @@ class Cat: MockObject {
@Field<Animal>("bestFriend") public var bestFriend
@Field<[Animal]>("predators") public var predators
@Field<String>("species") public var species
@Field<GraphQLEnum<Species>>("speciesType") public var speciesType
}
}

Expand All @@ -331,3 +371,22 @@ class Height: MockObject {
@Field<Int>("inches") public var inches
}
}

enum Species: String, EnumType {
case canine
case feline
}

struct MockCustomScalar: CustomScalarType, Hashable {
let value: Int

init(value: Int) {
self.value = value
}

init(_jsonValue value: ApolloAPI.JSONValue) throws {
self.value = value as! Int
}

var _jsonValue: ApolloAPI.JSONValue { value }
}