Kakapo 2.1.0

Kakapo 2.1.0

测试已测试
语言语言 SwiftSwift
许可 MIT
发布最后发布2016 年 10 月
SPM支持 SPM

MPowJoan Romano 维护。



Kakapo 2.1.0

Kakapo partyparrot

codebeat badge

动态模拟服务器行为和响应。

内容

Kakapo 是一个动态模拟库。它允许您复制您的后端 API 和逻辑。
使用 Kakapo,您可以轻松基于您的 API 规范原型化您的应用程序。

为什么选择 Kakapo?

在测试网络请求时的常见方法是使用来自本地文件或记录请求的假网络响应进行存根。这有一些缺点

  • 当 API 更新时,所有文件都需要更新。
  • 必须生成和包含大量文件到项目中。
  • 仅是静态响应,只能用于单元测试,因为它们不反映后端行为和状态。

尽管这种方法可能仍然有效,但 Kakapo 将是您网络测试中的游戏改变者:它将使您在模拟后端行为时拥有完全的控制权。此外,这不仅是单元测试:您甚至可以在拥有真实服务之前先原型化您的应用程序!
使用 Kakapo,您可以仅创建 Swift 结构体/类/枚举,它们将自动序列化为 JSON。

地球上 70 亿人口

少于 150 只 Kakapo

时间是关键 向Kakapo恢复捐赠

特性

  • 动态模拟
  • 原型设计
  • 兼容 Swift 3.0(自 2.0.0 版本,master 分支)
  • 兼容 Swift 2.3(自 1.0.0 版本,feature/legacy-swift 分支)
  • 兼容 Swift 2.2(0.x.y 版本)
  • 兼容
  • 面向协议和可插入
  • 可以通过定义自定义序列化和自定义响应来完全自定义
  • 开箱即用的序列化
  • JSONAPI 支持

安装

使用 CocoaPods

use_frameworks!
pod 'Kakapo'

使用方法

注意:该项目还包含一个 README.playground。查看它以了解Kakapo的一些关键功能示例。

Kakapo采用了便于使用的设计理念。为了快速开始,您可以创建一个像这样的Router来拦截网络请求:

let router = Router.register("http://www.test.com")
router.get("/users") { request in
  return ["id" : 2, "name": "Kakapo"]
}

您可能会想知道动态部分在哪里;这里就是Kakapo的不同模块发挥作用的时候了

let store = Store()
store.create(User.self, number: 20)

router.get("/users") { request in
  return store.findAll(User.self)
}

现在,我们已经创建了20个随机的User对象,并模拟了我们的请求以返回它们。

让我们更详细地看看不同的功能

可序列化协议

Kakapo使用Serializable协议将对象序列化为JSON。只要类型符合此协议,任何类型都可以序列化。

struct User: Serializable {
  let name: String
}

let user = User(name: "Alex")
let serializedUser = user.serialized()
//  -> ["name": "Alex"]

还支持标准库类型:这意味着ArrayDictionaryOptional可以序列化。

let serializedUserArray = [user].serialized()
// -> [["name": "Alex"]]
let serializedUserDictionary = ["test": user].serialized()
// -> ["test": ["name": "Alex"]]

Router - 注册和拦截

Kakapo使用Router来跟踪要拦截的已注册端点。
您可以使用匹配已注册基本URL的任何相对路径,只要组件与请求的组件匹配。您可以使用通配符组件。

let router = Router.register("http://www.test.com")

// Will match http://www.test.com/users/28
router.get("/users/:id") { ... }

// Will match http://www.test.com/users/28/comments/123
router.get("/users/:id/comments/:comment_id") { ... }

处理程序必须返回一个定义为响应的Serializable对象,一旦匹配请求的URL,当Router拦截请求时,它会自动将处理程序返回的Serializable对象序列化并转换为Data

router.get("/users/:id") { request in
  return ["id": request.components["id"]!, "name": "Joan"]
}

现在,您已经准备好测试模拟的API;您可以像平时一样执行请求

let session = URLSession.shared
let url = URL(string: "http://www.test.com/users/1")!
session.dataTask(with: url) { (data, _, _) in
    // handle response
}.resume()

注意:查询参数不会影响路由匹配http://www.test.com/users/1?foo=bar 也会被匹配

在前面的例子中,处理程序返回了一个简单的Dictionary;虽然这是有效的,因为Dictionary已经是Serializable的,您还可以创建自己的符合Serializable的自定义实体。

struct User: Serializable {
    let firstName: String
    let lastName: String
    let id: String
}

router.get("/users/:id") { request in
  return User(firstName: "Joan", lastName: "Romano", id: request.components["id"]!)
}

当请求匹配时,RouteHandler会接收到一个Request对象,它代表您的请求,包括组件、查询参数、httpBody和httpHeaders。Request对象在构建动态响应时可能非常有用。

第三方库

支持使用Foundation网络API的第三方库,但您可能需要设置适当的URLSessionConfiguration
例如,要设置 Alamofire

let configuration = URLSessionConfiguration.default
configuration.protocolClasses = [Server.self]
let sessionManager = SessionManager(configuration: configuration)

利用存档 - 动态模拟

当使用存档与人共同rPid>来使用您的Router时,Kakapo将变得更加强大。您可以创建、插入、删除、更新或查找对象。

这使得您可以模拟API的行为,就像使用真实后端一样。这是Kakapo的 动态 部分。

要创建可以用于存档的实体,您的类型需要符合Storable协议。

struct Article: Storable, Serializable {
    let id: String
    let text: String

    init(id: String, store: Store) {
        self.id = id
        self.text = randomString() // you might use some faker library like Fakery!
    }
}

一个例子用法是获取特定的Article

let store = Store()
store.create(Article.self, number: 20)

router.get("/articles/:id") { request in
  let articleId = request.components["id"]!
  return store.find(Article.self, id: articleId)
}

当然,您可以执行符合您需求的任何逻辑.

router.post("/article/:id") { request in
    return store.insert { (id) -> Article in
        return Article(id: id, text: "text from the body")
    }
}

router.del("/article/:id") { request in
  let articleId = request.components["id"]!
  let article = store.find(Article.self, id: articleId)!
  try! store.delete(article)

  return ["status": "success"]
}

CustomSerializable

可序列化中,我们描述了如何序列化您的类。默认情况下,序列化将通过Mirror(使用Swift的反射)递归地序列化其实例的属性。

当需要不同的行为时,您可以改为实行CustomSerializable以提供自定义的序列化。

例如,Array使用CustomSerializable返回一个包含其序列化元素的Array。类似地,Dictionary通过创建一个具有相同键和序列化值的Dictionary进行序列化。

有关CustomSerializable的其他示例以及如何使用它创建更复杂的序列化,请查看JSONAPISerializer实现。

JSONAPI

由于Kakapo在设计时考虑了JSONAPI支持,JSONAPISerializer能够将您的实体序列化为符合jsonapi.org的JSON。

为了符合JSONAPI的序列化,您的实体需要符合JSONAPIEntity协议。

让我们看看一个例子

struct Cat: JSONAPIEntity {
    let id: String
    let name: String
}

struct User: JSONAPIEntity {
    let id: String
    let name: String
    let cats: [Cat]
}

请注意,JSONAPIEntity对象已经是Serializable的,并且您可以与您的Routers一起使用它们。然而,为了完全遵循JSONAPI结构在您的响应中,您应该将它们包装到JSONAPISerializer结构中

router.get("/users/:id") { request in
  let cats = [Cat(id: "33", name: "Joan"), Cat(id: "44", name: "Hez")]
  let user = User(id: "11", name: "Alex", cats: cats)
  return JSONAPISerializer(user)
}

使用属性策略扩展null值

在将数据序列化为JSON时,您可能希望将属性值表示为null。为此,您可以使用PropertyPolicy枚举。它类似于Optional,提供了一个额外的.null情况

public enum PropertyPolicy<Wrapped>: CustomSerializable {
    case None
    case Null
    case Some(Wrapped)
}

它的唯一目的是以3种不同的方式序列化,以涵盖Optional属性的所有可能行为。PropertyPolicy的工作方式与Optional属性完全相同

  • .none -> 属性不包含在序列化中
  • .some(wrapped) -> 序列化wrapped

附加情况.null,在转换为json时序列化为null

PropertyPolicy<Int>.none.serialized() // nil
PropertyPolicy<Int>.null.serialized() // NSNull
PropertyPolicy<Int>.some(1).serialized() // 1

键自定义 - 序列化转换器

序列化生成的JSON键直接反映了您实体的属性名称。然而,您可能需要不同的行为。例如,许多API使用snake_case键,但几乎所有人都使用Swift中的camelCase属性。
要转换键,可以使用SerializationTransformer。遵守此协议的对象能够在序列化时间转换包装对象的键。

对于具体的实现,请查看SnakecaseTransformer:一个实现SerializationTransformer的结构体,用于将键转换为蛇形命名

let user = User(userName: "Alex")
let serialized = SnakecaseTransformer(user).serialized()
print(serialized) // [ "user_name" : "Alex" ]

使用ResponseFieldsProvider自定义响应

如果您的响应需要指定状态码(默认将为200)和/或头部字段,则可以利用ResponseFieldsProvider来自定义您的响应。

Kakapo在Response结构体中提供了默认的ResponseFieldsProvider实现,您可以使用它来包裹您的Serializable对象

router.get("/users/:id"){ request in
    return Response(statusCode: 400, body: user, headerFields: ["access_token" : "094850348502"])
}

let url = URL(string: "http://www.test.com/users/2")!
session.dataTaskWithURL() { (data, response, _) in
    let allHeaders = response.allHeaderFields
    let statusCode = response.statusCode
    print(allHeaders["access_token"]) // 094850348502
    print(statusCode) // 400
}.resume()

否则,您的Serializable对象可以直接实行协议:查看JSONAPIError以获取另一个示例。

路线图

尽管Kakapo已经准备好使用,但它并不打算发送到App Store,尽管你也可以这样做!实际上,你可能会在一些苹果店内看到它的实际应用,因为曾用它来模拟Runtastic演示应用的某些功能;然而,它还处于早期阶段,我们很乐意听取您的意见。如果您有任何问题、反馈或想提出新功能,我们鼓励您打开一个issue。

  • 完全支持JSON API #67
  • 反转和递归关系 #16
  • 为常见的JSON规范定制序列化器

示例

新闻源 BuddyBuild

请确保检查我们用Kakapo创建的演示应用:一个原型新闻源应用,允许用户创建新帖子并点赞/取消点赞。
要快速尝试,请使用:pod try Kakapo

作者

@MP0w - @zzarcon - @joanromano