数据构建者(实验性)
⚠️ 数据构建者是实验性的,可能发生变化。如果您对它们有任何反馈,请通过GitHub问题或Kotlin Slack社区提出。
Apollo Kotlin 为您生成模型和解析器,以从您的网络响应中创建这些模型的实例。然而,在测试或其他场合,有时手动使用已知值实例化模型非常有用。这样做并不像看上去那么简单,尤其是当使用 片段 时。operationBased
模型需要实例化每个 片段,以及为每个复合类型选择一个合适的 __typename
。数据构建器通过提供与 Json 文档 结构匹配的构建器,使这个过程变得更简单。
注意:Apollo Kotlin 的早期版本使用了测试构建器。数据构建器是测试构建器的一个简化版本,同时与自定义标量也更兼容。如果您正在寻找测试构建器的文档,它仍然可以在 这里 找到。
启用数据构建器
要启用数据,将 generateDataBuilders
选项设置为 true
:
apollo {service("service") {// ...// Enable data builder generationgenerateDataBuilders.set(true)}}
这将为您模式中的每个复合类型生成构建器,以及为您的每个操作生成一个辅助函数 Data {}
。
示例用法
假设我们正在构建一个使用以下 查询 的模拟结果的测试:
query HeroForEpisode($ep: Episode!) {hero(episode: $ep) {firstNamelastNameageship {modelspeed}friends {firstNamelastName}... on Droid {primaryFunction}... on Human {height}}}
以下是如何使用对应的模拟结果数据构建器:
@Testfun test() {val data = HeroForEpisodeQuery.Data {// Set values for particular fields of the queryhero = buildHuman {firstName = "John"age = 42friends = listOf(buildHuman {firstName = "Jane"},buildHuman {lastName = "Doe"})ship = buildStarship {model = "X-Wing"}}}assertEquals("John", data.hero.firstName)assertEquals(42, data.hero.age)}
在这个例子中,hero
字段 是一个具有指定 firstName
和 age
值的 Human
对象。对于 lastName
和 height
,将自动填充模拟值。同样,飞船的速度、第一位朋友的姓氏和第二位朋友的名字的值也将自动填充。
您可以将上面的 buildHuman
替换为 buildDroid
来创建一个 Droid
对象。
别名
由于数据生成器是 schema-based,而 别名 在您的查询中定义,因此代码生成器无法生成针对这些字段的 字段。相反,您需要显式指定它们。
给定如下 查询:)
query GetHeroes {luke: hero(id: "1002") {name}leia: hero(id: "1003") {name}}
您可以这样生成一个模拟数据模型
val data = GetHeroes.Data {this["luke"] = buildHumanHero {name = "Luke"}this["leia"] = buildHumanHero {name = "Leia"}}
@skip
和 @include
指令
默认情况下,数据生成器与您的 schema 中的类型匹配。如果一个 字段是非空白的,您将不得不提供一个值或将默认 解析器提供的一个值。对于 @skip
和 @include
指令来说这是一个问题,因为即使在非空的情况下,字段也可能不存在。为了考虑这种情况,使用与 别名 相同的语法。
query Skip($skip: Boolean!) {nonNullableInt @skip(if: $skip)}
您可以这样生成一个模拟数据模型
val data = SkipQuery.Data {this["nonNullableInt"] = Optional.Absent}assertNull(data.nonNullableInt)
配置默认字段值
为了将默认值分配给 字段,数据生成器使用 FakeResolver
接口的某个实现。默认情况下,它们使用 DefaultFakeResolver
的一个实例。
DefaultFakeResolver
给每个 String
字段 的默认值是该字段的名称,并在分配默认值给 Int
字段时递增计数器。它为其他类型定义了类似的行为。
您可以使用自己的 own FakeResolver
实现(可选地委托给 DefaultTestResolver
以获得先行优势)。然后,您可以将这个实现作为参数传递给 Data
函数,如下所示:
// A TestResolver implementation that assigns -1 to all Int fieldsclass MyFakeResolver : FakeResolver {private val delegate = DefaultFakeResolver(__Schema.all)override fun resolveLeaf(context: FakeResolverContext): Any {return when (context.mergedField.type.leafType().name) {"Int" -> -1 // Always use -1 for Intselse -> delegate.resolveLeaf(context)}}override fun resolveListSize(context: FakeResolverContext): Int {// Delegate to the default behaviourreturn delegate.resolveListSize(context)}override fun resolveMaybeNull(context: FakeResolverContext): Boolean {// Neverreturn false}override fun resolveTypename(context: FakeResolverContext): String {// Delegate to the default behaviourreturn delegate.resolveTypename(context)}}@Testfun test() {val data = HeroForEpisodeQuery.Data(resolver = myTestResolver) {hero = buildHuman {firstName = "John"}}// Unspecified Int field is -1assertEquals(-1, data.hero.age)}