测试模拟
当你使用你生成的操作模型进行单元测试时,通常需要创建带有模拟数据的响应模型。Apollo iOS提供了生成的测试模拟,以简化此过程。
用途
生成的测试模拟以类型安全的方式模拟响应模型。与处理繁杂且容易出错的JSON数据相比,测试模拟使模拟更安全、更简洁、可重用。它们是可变的,Xcode中提供代码完成,并随着您模式和操作的更改自动更新
使用JSON模拟GraphQL响应
由于生成的响应模型由JSON字典支持,因此在不使用生成的测试模拟的情况下使用模拟数据初始化它们既冗长又容易出错。您需要创建结构正好与预期网络响应一致的字符串类型JSON字典,以确保您的模型能够正确解析。
let data: [String: Any] = ["data": ["__typename": "Query","hero": ["__typename": "Human","id": "123","name": "Obi-wan Kenobi","friends": [["__typename": "Human","id": "456","name": "Padme Amidala"],["__typename": "Droid","id": "789","name": "C-3PO"]]]]]let model = try HeroAndFriendsQuery.Data(data: data)XCTAssertEqual(model.hero.friends[1].name, "C-3PO")
在测试中构建和维护这些JSON字典既繁琐又费时。
使用生成的测试模拟模拟GraphQL响应
生成的测试模拟提供了一种类型安全的方式来构建响应模式的模拟,而不需要使用字符串类型的JSON。
let mock = Mock<Query>(hero: Mock<Human>(id: "123",name: "Obi-wan Kenobi",friends: [Mock<Human>(id: "456",name: "Padme Amidala"),Mock<Droid>(id: "780",name: "C-3PO")]))let model = HeroAndFriendsQuery.Data.from(mock)XCTAssertEqual(model.hero.friends[1].name, "C-3PO")
设置测试模拟
要生成与您的架构类型和操作模型一起的测试模拟,请配置您的代码生成配置,使其包含它们,如代码生成配置 - 测试模拟中所述。
一旦生成了测试模拟并将其与单元测试的目标相关联后,您将需要导入ApolloTestSupport
到您的单元测试中以开始使用。
import MyTestMocksTargetimport ApolloTestSupportimport XCTestclass HeroAndFriendsQueryFetchingTests: XCTestCase {...}
用法
字段在一个GraphQL操作中可以是抽象类型(interface
或union
)。这意味着我们并不总是知道响应模型中某个字段的具体类型。使用类型条件进行操作时,响应对象的可能值会根据字段返回的对象类型而有很大差异。
因此,直接为我们的操作响应模型生成初始化器将需要为每个响应对象生成数十或数百个不同的初始化器。Apollo iOS根据您的模式对象类型生成MockObject
类型。您可以创建一个基于您模式的实体对象类型的模拟,并在其上设置字段。然后,您可以使用此模拟对象初始化任何响应模型。这允许您为对象构建模拟数据,并将它们与多个测试和响应模型一起使用。
创建测试模拟
您可以通过初始化泛型Mock<MockObject>
类并指定一个MockObject
类型来创建任何对象类型的模拟。
因为测试模拟需要您使用一个具体的对象类型,所以__typename
字段自动包含在每个对象在响应模型中使用时。
let grevious = Mock<Droid>()print(grevious.__typename) // "Droid"
在测试模拟上设置属性
每个MockObject
都会为可能在该对象类型上获取的所有字段生成生成属性,所有操作都在您的项目中。如果您的GraphQL模式中的一个字段从未被您的应用程序获取,那么它将不会在您的测试模拟对象上生成。
在单元测试中使用测试模拟时,您可能只关心响应对象上的所需字段的子集。测试模拟不需要您为您测试中不使用的任何字段初始化值。
您可以不提供数据来初始化它们,然后仅设置与您的测试相关的字段。Apollo iOS还为每个具体的对象类型生成了方便的初始化器。
let grevious = Mock<Droid>()grevious.name = "General Grevious"grevious.numberOfArms = 4let dooku = Mock<Human>()dooku.name = "Count Dooku"grevious.friends = [dooku]
let grevious = Mock<Droid>(name: "General Grevious",numberOfArms: 4,friends: [Mock<Human>(name: "Count Dooku")])
从测试模拟创建响应模型
初始化您的模拟对象后,您可以使用它们构建任何生成的响应模型。您不需要为整个操作'的响应模型创建模拟,只需测试中使用的对象即可。您还可以将模拟转换为生成的片段模型。
所有生成的响应模型都遵循SelectionSet
协议。要使用测试模拟创建模型,请在模型的类型上调用from(_ mock:
)。
let mockLuke = Mock<Human>(name: "Luke Skywalker")let heroQueryModel = HeroAndFriendsQuery.Data.Hero.from(mockLuke)let mockLeia = Mock<Human>(name: "Leia Organa")let heroDetailsFragmentModel = HeroDetails.from(mockLeia)
然后您可以在单元测试中使用创建的响应模型。
注意:确保在测试执行期间会访问的所有模拟对象的属性都进行了设置。如果您的程序在响应模型上访问一个必需的属性,但测试模拟不包含该字段,您的测试将崩溃!
重复使用测试模拟
相同的测试模拟可以用来创建多个不同的响应模型。
let mock = Mock<Human>(name: "Luke Skywalker")let hero = HeroAndFriendsQuery.Data.Hero.from(mock)let heroDetails = HeroDetailsFragment.from(mock)let mutationResponseModel = HeroDetailsMutation.Data.Hero(mock)
在Mock<MockObject>
类中,生成的响应模型是具有值语义的结构。这意味着当您创建一个响应模型时,它会创建模拟数据的自己的副本。如果您在创建响应模型后更改测试模拟的数据,模型的数据将不会受到影响。
let mock = Mock<Human>(name: "Luke Skywalker")let hero = HeroAndFriendsQuery.Data.Hero.from(mock)print(hero.name) // "Luke Skywalker"mock.name = "Leia Organa"let heroDetails = HeroDetailsFragment.from(mock)print(heroDetails.name) // "Leia Organa"/// The `hero` has not had it's data mutated.print(hero.name) // "Luke Skywalker"