# Upgraden naar 4.0 Deze handleiding laat zien hoe u een bestaand Vapor 3.x project kunt opwaarderen naar 4.x. Deze handleiding probeert alle officiële pakketten van Vapor te behandelen, evenals enkele veelgebruikte leveranciers. Als u merkt dat er iets ontbreekt, dan is [Vapor's team chat](https://discord.gg/vapor) een goede plaats om om hulp te vragen. Issues en pull requests worden ook op prijs gesteld. ## Afhankelijkheden Om Vapor 4 te gebruiken, hebt u Xcode 11.4 en macOS 10.15 of hoger nodig. De Install sectie van de docs gaat over het installeren van afhankelijkheden. ## Package.swift De eerste stap bij het upgraden naar Vapor 4 is het bijwerken van de afhankelijkheden van uw pakket. Hieronder staat een voorbeeld van een bijgewerkt Package.swift bestand. U kunt ook het bijgewerkte [sjabloon Package.swift](https://github.com/vapor/template/blob/master/Package.swift) bekijken. ```diff -// swift-tools-version:4.0 +// swift-tools-version:5.2 import PackageDescription let package = Package( name: "api", + platforms: [ + .macOS(.v10_15), + ], dependencies: [ - .package(url: "https://github.com/vapor/fluent-postgresql.git", from: "1.0.0"), + .package(url: "https://github.com/vapor/fluent.git", from: "4.0.0-rc"), + .package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.0.0-rc"), - .package(url: "https://github.com/vapor/jwt.git", from: "3.0.0"), + .package(url: "https://github.com/vapor/jwt.git", from: "4.0.0-rc"), - .package(url: "https://github.com/vapor/vapor.git", from: "3.0.0"), + .package(url: "https://github.com/vapor/vapor.git", from: "4.3.0"), ], targets: [ .target(name: "App", dependencies: [ - "FluentPostgreSQL", + .product(name: "Fluent", package: "fluent"), + .product(name: "FluentPostgresDriver", package: "fluent-postgres-driver"), - "Vapor", + .product(name: "Vapor", package: "vapor"), - "JWT", + .product(name: "JWT", package: "jwt"), ]), - .target(name: "Run", dependencies: ["App"]), - .testTarget(name: "AppTests", dependencies: ["App"]) + .target(name: "Run", dependencies: [ + .target(name: "App"), + ]), + .testTarget(name: "AppTests", dependencies: [ + .target(name: "App"), + ]) ] ) ``` Van alle pakketten die opgewaardeerd zijn voor Vapor 4 zal het hoofdversienummer met één verhoogd worden. !!! warning "Waarschuwing" De `-rc` pre-release aanduiding wordt gebruikt omdat sommige pakketten van Vapor 4 nog niet officieel zijn uitgebracht. ### Oude Pakketten Sommige pakketten zijn misschien nog niet geüpgraded. Als u er tegenkomt, dien dan een probleem in om de auteur te laten weten. Sommige Vapor 3 pakketten zijn afgeschreven, zoals: - `vapor/auth`: Nu opgenomen in Vapor. - `vapor/core`: Opgenomen in verschillende modules. - `vapor/crypto`: Vervangen door SwiftCrypto. - `vapor/multipart`: Nu opgenomen in Vapor. - `vapor/url-encoded-form`: Nu opgenomen in Vapor. - `vapor-community/vapor-ext`: Nu opgenomen in Vapor. - `vapor-community/pagination`: Nu onderdeel van Fluent. - `IBM-Swift/LoggerAPI`: Vervangen door SwiftLog. ### Fluent Afhankelijkheid `vapor/fluent` moet nu als een aparte dependency worden toegevoegd aan uw dependencies lijst en targets. Alle database-specifieke pakketten zijn aangevuld met `-driver` om de eis voor `vapor/fluent` duidelijk te maken. ```diff - .package(url: "https://github.com/vapor/fluent-postgresql.git", from: "1.0.0"), + .package(url: "https://github.com/vapor/fluent.git", from: "4.0.0-rc"), + .package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.0.0-rc"), ``` ### Platvormen Vapor's package manifests ondersteunen nu expliciet macOS 10.15 en hoger. Dit betekent dat uw pakket ook platformondersteuning moet specificeren. ```diff + platforms: [ + .macOS(.v10_15), + ], ``` Vapor kan in de toekomst extra ondersteunde platforms toevoegen. Uw pakket mag een subset van deze platformen ondersteunen zolang het versienummer gelijk is aan of hoger is dan de minimum versie-eisen van Vapor. ### Xcode Vapor 4 maakt gebruik van Xcode 11's eigen SPM ondersteuning. Dit betekent dat u niet langer `.xcodeproj` bestanden hoeft te genereren. Het openen van de map van uw project in Xcode zal automatisch SPM herkennen en de afhankelijkheden binnenhalen. U kunt uw project openen in Xcode met `vapor xcode` of `open Package.swift`. Nadat u Package.swift hebt bijgewerkt, moet u wellicht Xcode sluiten en de volgende mappen uit de hoofdmap verwijderen: - `Package.resolved` - `.build` - `.swiftpm` - `*.xcodeproj` Zodra uw bijgewerkte pakketten succesvol opgelost zijn, zou u compilerfouten moeten zien--waarschijnlijk heel wat. Maak u geen zorgen! We zullen u tonen hoe ze te herstellen. ## Run De eerste opdracht is om het bestand `main.swift` van uw Run module aan te passen aan het nieuwe formaat. ```swift import App import Vapor var env = try Environment.detect() try LoggingSystem.bootstrap(from: &env) let app = Application(env) defer { app.shutdown() } try configure(app) try app.run() ``` De inhoud van het bestand `main.swift` vervangt het bestand `app.swift` van de App module, dus u kunt dat bestand verwijderen. ## App Laten we eens kijken hoe we de basis App module structuur kunnen bijwerken. ### configure.swift De `configure` methode moet veranderd worden om een instantie van `Application` te accepteren. ```diff - public func configure(_ config: inout Config, _ env: inout Environment, _ services: inout Services) throws + public func configure(_ app: Application) throws ``` Hieronder staat een voorbeeld van een bijgewerkte configureermethode. ```swift import Fluent import FluentSQLiteDriver import Vapor // Wordt aangeroepen voordat uw toepassing initialiseert. public func configure(_ app: Application) throws { // Serveert bestanden uit `Public/` directory // app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory)) // Configureer SQLite database app.databases.use(.sqlite(.file("db.sqlite")), as: .sqlite) // Migraties configureren app.migrations.add(CreateTodo()) try routes(app) } ``` Syntax veranderingen voor het configureren van zaken als routing, middleware, fluent, en meer staan hieronder vermeld. ### boot.swift De inhoud van `boot` kan in de `configure` methode geplaatst worden, omdat deze nu de applicatie instantie accepteert. ### routes.swift De `routes` methode moet veranderd worden om een instantie van `Application` te accepteren. ```diff - public func routes(_ router: Router, _ container: Container) throws + public func routes(_ app: Application) throws ``` Meer informatie over wijzigingen in de routingsyntaxis wordt hieronder vermeld. ## Services Vapor 4's services APIs zijn vereenvoudigd om het makkelijker te maken voor u om services te ontdekken en te gebruiken. Services worden nu getoond als methods en properties op `Application` en `Request` waardoor de compiler u kan helpen ze te gebruiken. Laten we, om dit beter te begrijpen, eens naar een paar voorbeelden kijken. ```diff // Verander de standaardpoort van de server in 8281 - services.register { container -> NIOServerConfig in - return .default(port: 8281) - } + app.http.server.configuration.port = 8281 ``` In plaats van een `NIOServerConfig` te registreren bij services, wordt server configuratie nu getoond als eenvoudige eigenschappen op Application die kunnen worden overschreven. ```diff // Registreer cors middleware let corsConfiguration = CORSMiddleware.Configuration( allowedOrigin: .all, allowedMethods: [.POST, .GET, .PATCH, .PUT, .DELETE, .OPTIONS] ) let corsMiddleware = CORSMiddleware(configuration: corsConfiguration) - var middlewares = MiddlewareConfig() // Create _empty_ middleware config - middlewares.use(corsMiddleware) - services.register(middlewares) + app.middleware.use(corsMiddleware) ``` In plaats van een `MiddlewareConfig` aan te maken en te registreren bij services, worden middleware nu blootgesteld als een eigenschap op Application waaraan toegevoegd kan worden. ```diff // Doe een verzoek in een route handler. - try req.make(Client.self).get("https://vapor.codes") + req.client.get("https://vapor.codes") ``` Net als Applicatie, stelt Request ook diensten bloot als eenvoudige eigenschappen en methoden. Request-specifieke services moeten altijd worden gebruikt binnen een routeafsluiting. Dit nieuwe service patroon vervangt de `Container`, `Service`, en `Config` types uit Vapor 3. ### Providers Providers zijn niet langer verplicht om pakketten van derden te configureren. Elk pakket breidt in plaats daarvan Applicatie en Verzoek uit met nieuwe eigenschappen en methoden voor configuratie. Kijk eens hoe Leaf is geconfigureerd in Vapor 4. ```diff // Gebruik Leaf voor het renderen van views. - try services.register(LeafProvider()) - config.prefer(LeafRenderer.self, for: ViewRenderer.self) + app.views.use(.leaf) ``` Om Leaf te configureren, gebruik de `app.leaf` eigenschap. ```diff // Uitschakelen van de caching van de bladweergave. - services.register { container -> LeafConfig in - return LeafConfig(tags: ..., viewsDir: ..., shouldCache: false) - } + app.leaf.cache.isEnabled = false ``` ### Environment De huidige omgeving (productie, ontwikkeling, etc) kan worden opgevraagd via `app.environment`. ### Aangepaste Services Aangepaste diensten die voldoen aan het `Service` protocol en geregistreerd zijn bij de container in Vapor 3 kunnen nu worden uitgedrukt als uitbreidingen op ofwel Applicatie of Verzoek. ```diff struct MyAPI { let client: Client func foo() { ... } } - extension MyAPI: Service { } - services.register { container -> MyAPI in - return try MyAPI(client: container.make()) - } + extension Request { + var myAPI: MyAPI { + .init(client: self.client) + } + } ``` Deze dienst kan dan worden benaderd met de extensie in plaats van `make`. ```diff - try req.make(MyAPI.self).foo() + req.myAPI.foo() ``` ### Aangepaste Providers De meeste aangepaste diensten kunnen worden geïmplementeerd met behulp van extensies zoals getoond in de vorige sectie. Het kan echter nodig zijn om voor sommige geavanceerde diensten in te haken op de levenscyclus van de applicatie of gebruik te maken van opgeslagen eigenschappen. Applicatie's nieuwe `Lifecycle` helper kan gebruikt worden om lifecycle handlers te registreren. ```swift struct PrintHello: LifecycleHandler { func willBoot(_ app: Application) throws { print("Hello!") } } app.lifecycle.use(PrintHello()) ``` Om waarden op te slaan op Application, kun je de nieuwe `Storage` helper gebruiken. ```swift struct MyNumber: StorageKey { typealias Value = Int } app.storage[MyNumber.self] = 5 print(app.storage[MyNumber.self]) // 5 ``` Toegang tot `app.storage` kan worden verpakt in een instelbare berekende eigenschap om een beknopte API te maken. ```swift extension Application { var myNumber: Int? { get { self.storage[MyNumber.self] } set { self.storage[MyNumber.self] = newValue } } } app.myNumber = 42 print(app.myNumber) // 42 ``` ## NIO Vapor 4 stelt nu SwiftNIO's async APIs direct beschikbaar en probeert niet om methoden als `map` en `flatMap` te overloaden of alias types als `EventLoopFuture`. Vapor 3 voorzag in overloads en aliassen voor achterwaartse compatibiliteit met vroege beta versies die uitgebracht werden voordat SwiftNIO bestond. Deze zijn verwijderd om verwarring met andere SwiftNIO compatibele pakketten te verminderen en beter de best practice aanbevelingen van SwiftNIO te volgen. ### Async naamgeving veranderingen De meest voor de hand liggende verandering is dat de `Future` typealias voor `EventLoopFuture` is verwijderd. Dit kan vrij eenvoudig worden opgelost met een find and replace. Verder ondersteunt NIO niet de `to:` labels die Vapor 3 toevoegde. Gezien Swift 5.2's verbeterde type-inferentie, is `to:` nu toch minder nodig. ```diff - futureA.map(to: String.self) { ... } + futureA.map { ... } ``` Methoden die voorafgegaan worden door `new`, zoals `newPromise` zijn veranderd in `make` om beter aan te sluiten bij de Swift stijl. ```diff - let promise = eventLoop.newPromise(String.self) + let promise = eventLoop.makePromise(of: String.self) ``` `catchMap` is niet langer beschikbaar, maar NIO's methodes zoals `mapError` en `flatMapErrorThrowing` zullen in plaats daarvan werken. Vapor 3's globale `flatMap` methode voor het combineren van meerdere futures is niet langer beschikbaar. Deze kan worden vervangen door gebruik te maken van NIO's `and` methode om veel futures samen te voegen. ```diff - flatMap(futureA, futureB) { a, b in + futureA.and(futureB).flatMap { (a, b) in // Doe iets met a en b. } ``` ### ByteBuffer Veel methoden en eigenschappen die voorheen `Data` gebruikten, gebruiken nu NIO's `ByteBuffer`. Dit type is een krachtiger en performanter byte opslagtype. U kunt meer lezen over de API in [SwiftNIO's ByteBuffer docs](https://swiftpackageindex.com/apple/swift-nio/main/documentation/niocore/bytebuffer). Om een `ByteBuffer` terug te converteren naar `Data`, gebruik je: ```swift Data(buffer.readableBytesView) ``` ### Throwing map / flatMap De moeilijkste verandering is dat `map` en `flatMap` niet meer kunnen gooien. `map` heeft een werpversie met de (enigszins verwarrende) naam `flatMapThrowing`. `flatMap` heeft echter geen werpende tegenhanger. Dit kan betekenen dat je wat asynchrone code moet herstructureren. Maps die _niet_ gooien zouden prima moeten blijven werken. ```swift // Non-throwing map. futureA.map { a in return b } ``` Maps die _wel_ gooien moeten worden hernoemd naar `flatMapThrowing`. ```diff - futureA.map { a in + futureA.flatMapThrowing { a in if ... { throw SomeError() } else { return b } } ``` Flat-maps die _niet_ gooien zouden prima moeten blijven werken. ```swift // Non-throwing flatMap. futureA.flatMap { a in return futureB } ``` In plaats van het gooien van een fout in een flat-map, retourneer een toekomstige fout. Als de fout afkomstig is van een andere werpmethode, kan de fout worden opgevangen in een do / catch en geretourneerd als een future. ```swift // Een gevangen fout als een toekomst teruggeven. futureA.flatMap { a in do { try doSomething() return futureB } catch { return eventLoop.makeFailedFuture(error) } } ``` Het aanroepen van methodes kan ook worden omgezet in een `flatMapThrowing` en geketend worden met behulp van tuples. ```swift // Methode voor gooien omgebouwd tot flatMapThrowing met tuple-ketting. futureA.flatMapThrowing { a in try (a, doSomeThing()) }.flatMap { (a, result) in // resultaat is de waarde van doSomething. return futureB } ``` ## Routing Routes worden nu rechtstreeks in de applicatie geregistreerd. ```swift app.get("hello") { req in return "Hello, world" } ``` Dit betekent dat je niet langer een router hoeft te registreren bij services. Geef gewoon de applicatie door aan je `routes` methode en begin met het toevoegen van routes. Alle methodes die beschikbaar zijn op `RoutesBuilder` zijn beschikbaar op `Application`. ### Synchrone Content Het decoderen van de inhoud van verzoeken verloopt nu synchroon. ```swift let payload = try req.content.decode(MyPayload.self) print(payload) // MyPayload ``` Dit gedrag kan worden opgeheven door register routes die gebruik maken van de `.stream` body collectie strategie. ```swift app.on(.POST, "streaming", body: .stream) { req in // Het verzoek is nu asynchroon. req.body.collect().map { buffer in HTTPStatus.ok } } ``` ### Comma-separated paths Paden moeten nu gescheiden zijn door komma's en mogen geen `/` bevatten voor consistentie. ```diff - router.get("v1/users/", "posts", "/comments") { req in + app.get("v1", "users", "posts", "comments") { req in // Handle request. } ``` ### Route parameters Het `Parameter` protocol is verwijderd ten gunste van expliciet benoemde parameters. Dit voorkomt problemen met dubbele parameters en ongeordende fetching van parameters in middleware en route handlers. ```diff - router.get("planets", String.parameter) { req in - let id = req.parameters.next(String.self) + app.get("planets", ":id") { req in + let id = req.parameters.get("id") return "Planet id: \(id)" } ``` Het gebruik van routeparameters met modellen wordt vermeld in het Fluent-gedeelte. ## Middleware `MiddlewareConfig` is hernoemd naar `MiddlewareConfiguration` en is nu een property op Application. U kunt middleware aan uw app toevoegen met `app.middleware`. ```diff let corsMiddleware = CORSMiddleware(configuration: ...) - var middleware = MiddlewareConfig() - middleware.use(corsMiddleware) + app.middleware.use(corsMiddleware) - services.register(middlewares) ``` Middleware kan niet langer geregistreerd worden op type-naam. Initialiseer de middleware eerst alvorens te registreren. ```diff - middleware.use(ErrorMiddleware.self) + app.middleware.use(ErrorMiddleware.default(environment: app.environment)) ``` Om alle standaard middleware te verwijderen, stel `app.middleware` in op een lege config met: ```swift app.middleware = .init() ``` ## Fluent Fluent's API is nu database agnostisch. U kunt alleen `Fluent` importeren. ```diff - import FluentMySQL + import Fluent ``` ### Models Alle modellen gebruiken nu het `Model` protocol en moeten klassen zijn. ```diff - struct Planet: MySQLModel { + final class Planet: Model { ``` Alle velden worden gedeclareerd met `@Field` of `@OptionalField` property wrappers. ```diff + @Field(key: "name") var name: String + @OptionalField(key: "age") var age: Int? ``` Het ID van een model moet worden gedefinieerd met de `@ID` property wrapper. ```diff + @ID(key: .id) var id: UUID? ``` Modellen die een identifier gebruiken met een aangepaste sleutel of type moeten `@ID(custom:)` gebruiken. Van alle modellen moet de naam van hun tabel of verzameling statisch gedefinieerd zijn. ```diff final class Planet: Model { + static let schema = "Planet" } ``` Alle modellen moeten nu een lege initializer hebben. Aangezien alle eigenschappen property wrappers gebruiken, kan deze leeg zijn. ```diff final class Planet: Model { + init() { } } ``` Model's `save`, `update`, en `create` retourneren niet langer de model instantie. ```diff - model.save(on: ...) + model.save(on: ...).map { model } ``` Modellen kunnen niet langer worden gebruikt als route path componenten. Gebruik in plaats daarvan `find` en `req.parameters.get`. ```diff - try req.parameters.next(ServerSize.self) + ServerSize.find(req.parameters.get("size"), on: req.db) + .unwrap(or: Abort(.notFound)) ``` `Model.ID` is hernoemd naar `Model.IDValue`. Model tijdstempels worden nu aangegeven met de `@Timestamp` property wrapper. ```diff - static var createdAtKey: TimestampKey? = \.createdAt + @Timestamp(key: "createdAt", on: .create) var createdAt: Date? ``` ### Relaties Relaties worden nu gedefinieerd met property wrappers. Parent relaties gebruiken de `@Parent` property wrapper en bevatten intern de field property. De sleutel die aan `@Parent` wordt doorgegeven moet de naam zijn van het veld dat de identifier in de database opslaat. ```diff - var serverID: Int - var server: Parent { - parent(\.serverID) - } + @Parent(key: "serverID") + var server: Server ``` Children relaties gebruiken de `@Children` eigenschap wrapper met een sleutelpad naar de gerelateerde `@Parent`. ```diff - var apps: Children { - children(\.serverID) - } + @Children(for: \.$server) + var apps: [App] ``` Siblings relaties gebruiken de `@Siblings` property wrapper met sleutelpaden naar het pivot model. ```diff - var users: Siblings { - siblings() - } + @Siblings(through: Permission.self, from: \.$user, to: \.$company) + var companies: [Company] ``` Pivots zijn nu normale modellen die voldoen aan `Model` met twee `@Parent` relaties en nul of meer extra velden. ### Query De database context wordt nu benaderd via `req.db` in route handlers. ```diff - Planet.query(on: req) + Planet.query(on: req.db) ``` `DatabaseConnectable` is hernoemd naar `Database`. Sleutelpaden naar velden worden nu voorafgegaan door `$` om de eigenschap wrapper te specificeren in plaats van de veldwaarde. ```diff - filter(\.foo == ...) + filter(\.$foo == ...) ``` ### Migraties Modellen ondersteunen niet langer op reflectie gebaseerde automatische migraties. Alle migraties moeten handmatig worden geschreven. ```diff - extension Planet: Migration { } + struct CreatePlanet: Migration { + ... +} ``` Migraties zijn nu stringly typed en losgekoppeld van modellen en gebruiken het `Migration` protocol. ```diff - struct CreateGalaxy: <#Database#>Migration { + struct CreateGalaxy: Migration { ``` De `prepare` en `revert` methodes zijn niet langer statisch. ```diff - static func prepare(on conn: <#Database#>Connection) -> Future { + func prepare(on database: Database) -> EventLoopFuture ``` Het aanmaken van een schema builder gebeurt via een instance methode op `Database`. ```diff - <#Database#>Database.create(Galaxy.self, on: conn) { builder in - // Use builder. - } + var builder = database.schema("Galaxy") + // Use builder. ``` De `create`, `update`, en `delete` methods worden nu aangeroepen op de schema builder, vergelijkbaar met hoe query builder werkt. Velddefinities zijn nu stringent getypeert en volgen het patroon: ```swift field(, , ) ``` Zie onderstaand voorbeeld. ```diff - builder.field(for: \.name) + builder.field("name", .string, .required) ``` Schema bouw kan nu aaneengeschakeld worden zoals query bouwer. ```swift database.schema("Galaxy") .id() .field("name", .string, .required) .create() ``` ### Fluent Configuratie `DatabasesConfig` is vervangen door `app.databases`. ```swift try app.databases.use(.postgres(url: "postgres://..."), as: .psql) ``` `MigrationsConfig` is vervangen door `app.migrations`. ```swift app.migrations.use(CreatePlanet(), on: .psql) ``` ### Repositories Omdat de manier waarop services werken in Vapor 4 is veranderd, betekent dat ook dat de manier om database repositories te maken is veranderd. Je hebt nog steeds een protocol nodig zoals `UserRepository`, maar in plaats van een `final class` te maken die voldoet aan dat protocol, moet je in plaats daarvan een `struct` maken. ```diff - final class DatabaseUserRepository: UserRepository { + struct DatabaseUserRepository: UserRepository { let database: Database func all() -> EventLoopFuture<[User]> { return User.query(on: database).all() } } ``` U moet ook de conformiteit van `ServiceType` verwijderen, omdat deze niet meer bestaat in Vapor 4. ```diff - extension DatabaseUserRepository { - static let serviceSupports: [Any.Type] = [Athlete.self] - static func makeService(for worker: Container) throws -> Self { - return .init() - } - } ``` In plaats daarvan moet je een `UserRepositoryFactory` maken: ```swift struct UserRepositoryFactory { var make: ((Request) -> UserRepository)? mutating func use(_ make: @escaping ((Request) -> UserRepository)) { self.make = make } } ``` Deze fabriek is verantwoordelijk voor het retourneren van een `UserRepository` voor een `Request`. De volgende stap is het toevoegen van een extensie aan `Application` om uw fabriek te specificeren: ```swift extension Application { private struct UserRepositoryKey: StorageKey { typealias Value = UserRepositoryFactory } var users: UserRepositoryFactory { get { self.storage[UserRepositoryKey.self] ?? .init() } set { self.storage[UserRepositoryKey.self] = newValue } } } ``` Om het eigenlijke repository te gebruiken in een `Request` voeg je deze extensie toe aan `Request`: ```swift extension Request { var users: UserRepository { self.application.users.make!(self) } } ``` De laatste stap is het specificeren van de fabriek in `configure.swift`. ```swift app.users.use { req in DatabaseUserRepository(database: req.db) } ``` Je kunt nu je repository benaderen in je route handlers met: `req.users.all()` en eenvoudig de factory binnen tests vervangen. Als je een mocked repository in tests wilt gebruiken, maak dan eerst een `TestUserRepository` ```swift final class TestUserRepository: UserRepository { var users: [User] let eventLoop: EventLoop init(users: [User] = [], eventLoop: EventLoop) { self.users = users self.eventLoop = eventLoop } func all() -> EventLoopFuture<[User]> { eventLoop.makeSuccededFuture(self.users) } } ``` Je kunt nu deze mocked repository als volgt gebruiken in je tests: ```swift final class MyTests: XCTestCase { func test() throws { let users: [User] = [] app.users.use { TestUserRepository(users: users, eventLoop: $0.eventLoop) } ... } } ```