Test mocks


When unit testing code that uses your generated operation models, you will often want to create response models with mock data. Apollo iOS provides generated test mocks that facilitate this.

Purpose

Generated test mocks provide a type-safe way to mock your response models. Rather than dealing with cumbersome and error prone JSON data, test mocks make mocking safer, more concise, and reusable. They are mutable, offer code completion in Xcode, and update with new fields automatically as your schema and operations change.

Mocking GraphQL responses with JSON

Because the generated response models are backed by a JSON dictionary, initializing them with mock data without generated test mocks is verbose and error-prone. You need to create stringly-typed JSON dictionaries that are structured exactly like the expected network response to ensure that your models parse properly.

Swift
1let data: [String: Any] = [
2    "data": [
3        "__typename": "Query",
4        "hero": [
5            "__typename": "Human",
6            "id": "123",
7            "name": "Obi-wan Kenobi",
8            "friends": [
9                [
10                    "__typename": "Human",
11                    "id": "456",
12                    "name": "Padme Amidala"
13                ],
14                [
15                    "__typename": "Droid",
16                    "id": "789",
17                    "name": "C-3PO"
18                ]
19            ]
20        ]
21    ]
22]
23
24let model = try HeroAndFriendsQuery.Data(data: data)
25
26XCTAssertEqual(model.hero.friends[1].name, "C-3PO")

Constructing and maintaining these JSON dictionaries in your tests is cumbersome and time consuming.

Mocking GraphQL responses with generated test mocks

Generated test mocks provide a type-safe way to build mocks of your response models, without using stringly-typed JSON.

Swift
1let mock = Mock<Query>(
2    hero: Mock<Human>(
3        id: "123",
4        name: "Obi-wan Kenobi",
5        friends: [
6            Mock<Human>(
7                id: "456",
8                name: "Padme Amidala"
9            ),
10            Mock<Droid>(
11                id: "780",
12                name: "C-3PO"
13            )
14        ]
15    )
16)
17
18let model = HeroAndFriendsQuery.Data.from(mock)
19
20XCTAssertEqual(model.hero.friends[1].name, "C-3PO")

Setting up test mocks

To generate test mocks along side your schema types and operation models, configure your code generation configuration to include them as described in code generation configuration - Test mocks.

Once you've generated test mocks and linked them to your unit test target, you will need to import ApolloTestSupport in your unit tests to start using them.

Swift
1import MyTestMocksTarget
2import ApolloTestSupport
3import XCTest
4
5class HeroAndFriendsQueryFetchingTests: XCTestCase {
6    ...
7}

Usage

Fields in a GraphQL operation can be of abstract types (interface or union). This means we don't always know what type of object a field in a response model will be. When using type conditions in our operations, the possible values for a response object can be very different depending on what object type is returned for the field. Because of this, generating intializers for our operation response models directly would require tens or hundreds of different generated initializers for every response object.

Instead, Apollo iOS generates MockObject types based on your schema object types. You create a mock of a concrete object type from your schema as, and set fields on it. Then you can use this mock object to initialize any response model. This allows you to build mock data for objects and use them with multiple tests and response models.

Creating test mocks

You can create a mock of any object type by initializing the generic Mock<MockObject> class and specifying a MockObject type.

Because test mocks require you to use a concrete object type, the __typename field automatically included whenever the object is used in a response model.

Swift
1let grevious = Mock<Droid>()
2
3print(grevious.__typename) // "Droid"

Setting properties on test mocks

Each MockObject has generated properties for each field that could be fetched on that object type across all the operations in your project. If a field on your GraphQL schema is never fetched by your application, it won't be generated on your test mock objects.

When using your test mocks in a unit test, you may only be concerned about a subset of the required fields on the response object. Test mocks don't require you to initialize values for any fields you are not using in your tests.

You can initialize them with no data, and then set only the fields relevant for your test. Apollo iOS also generates convenience initializers for each concrete object type.

Swift
1let grevious = Mock<Droid>()
2grevious.name = "General Grevious"
3grevious.numberOfArms = 4
4
5let dooku = Mock<Human>()
6dooku.name = "Count Dooku"
7
8grevious.friends = [dooku]
Swift
1let grevious = Mock<Droid>(
2  name: "General Grevious",
3    numberOfArms: 4,
4    friends: [
5      Mock<Human>(name: "Count Dooku")
6    ]
7)

Creating response models from test mocks

Once you've initialized your mock objects, you can construct any of your generated response models with them. You do not need to create mocks for an entire operation's response model, just the objects your are using in your tests. You can also convert your mocks into your generated fragment models.

All generated response models conform to the SelectionSet protocol. To create a model with a test mock, call from(_ mock:) on the model's type.

Swift
1let mockLuke = Mock<Human>(name: "Luke Skywalker")
2let heroQueryModel = HeroAndFriendsQuery.Data.Hero.from(mockLuke)
3
4let mockLeia = Mock<Human>(name: "Leia Organa")
5let heroDetailsFragmentModel = HeroDetails.from(mockLeia)

You can then use the created response models in your unit tests.

Note: Make sure to set all of the properties on your mocks that are going to be accessed during your test's execution. If your program accesses a required property on a response model and it's test mock does not contain that field, your tests will crash!

Reusing test mocks

The same test mock can be used to create multiple different response models.

Swift
1let mock = Mock<Human>(name: "Luke Skywalker")
2
3let hero = HeroAndFriendsQuery.Data.Hero.from(mock)
4let heroDetails = HeroDetailsFragment.from(mock)
5let mutationResponseModel = HeroDetailsMutation.Data.Hero(mock)

While Mock<MockObject> a class, your generated response models are structs with value semantics. This means that when you create a response model, it creates it's own copy of the test mock's data. If you mutate the test mock's data after creating a response model, that model's data will be unaffected.

Swift
1let mock = Mock<Human>(name: "Luke Skywalker")
2
3let hero = HeroAndFriendsQuery.Data.Hero.from(mock)
4print(hero.name) // "Luke Skywalker"
5
6mock.name = "Leia Organa"
7let heroDetails = HeroDetailsFragment.from(mock)
8print(heroDetails.name) // "Leia Organa"
9
10/// The `hero` has not had it's data mutated.
11print(hero.name) // "Luke Skywalker"
Feedback

Edit on GitHub

Forums