Great, so you are using some helpers to fix this problem, too. What I'm concerned about is the less experienced developers who want to learn backend development in Swift, I think we should provide more guidance for them so it's easier for them to get into backend development. And especially since tests are a way too often neglected topic in iOS app projects (at least I've seen too many without any tests written at all), I think we should take this seriously as otherwise chances are high those developers will get frustrated with writing tests and neglect proper tests on backen projects, too. Tests are very important on backends though and as mentioned in this thread, I think Vapor is lacking proper documentation at the moment.
But we can change that easily, as you've stated that you have a helper, I'd like to share my helper structure for fixtures that I have currently in place, if you then also share yours we can maybe learn from each other and in the end write a documentation page which mentions an example structure.
I've setup a protocol called Fixture
in my AppTests
folder (in the Supporting
subfolder):
import Foundation
import Fluent
protocol Fixture: AnyObject {
associatedtype VariantType
static func fixture(on database: Database, variant: VariantType, overrides: ((Self) -> Void)?) throws -> Self
}
extension Fixture {
@discardableResult
static func fixtures(
on database: Database,
variant: VariantType,
count: Int,
enumeratedOverrides: ((Int) -> ((Self) -> Void))? = nil
) throws -> [Self] {
try (1 ... count).map { try fixture(on: database, variant: variant, overrides: enumeratedOverrides?($0)) }
}
}
Then, for each model type, I add an extension in the AppTests
folder (within the Fixtures
subfolder), here's a simple one for the User
model:
@testable import App
import Fluent
import Foundation
import HandySwift
extension User: Fixture {
enum Variant {
case minimal
}
static func fixture(on database: Database, variant: Variant, overrides: ((User) -> Void)? = nil) throws -> User {
let instance = User()
switch variant {
case .minimal:
instance.countryCode = ["DE", "US", "JP"].randomElement()!
instance.subTerritoryCode = ["BW", "CA", "13"].randomElement()!
instance.timeZoneSecondsFromGMT = Int(TimeInterval.hours([2, -9, 8].randomElement()!).seconds)
}
overrides?(instance)
try instance.save(on: database).wait()
return instance
}
}
(Yes, my User
type doesn't contain any personal information as name, age etc. which are typically used in User
examples. I'm working on a privacy respecting app right now which doesn't save any data I don't need, so sorry for the untypical example.)
Note that I'm specifying arrays at the moment and use randomElement()!
to get sample data. I'm planning to replace this part with a faker library later on, but I haven't found one that I like yet.
The basic idea here is that for each different sample data sets you create a new case
in the Variant
enum and provide the sample data for it in the fixture
function. There's also a function as an extension to Fixture
which supports creating an array of model objects at once.
Usage in tests is like this:
// create a single user and reference it locally
let user = try User.fixture(on: app.db, variant: .minimal)
// creating multiple users and reference them locally
let users = try User.fixtures(on: app.db, variant: .minimal, count: 5)
You can also use the overrides
or enumeratedOverrides
closure to do some custom changes to the generated model object on the usage side.
@0xTim What does your solution look like, can you extract an example so we can all learn? :)