A while back, I wanted to create a Cucumber implementation for Swift that "feels like SwiftUI, but for testing".
Haven't touched the project in a while because I had run into a wall, but I recently found out that this wall has been removed, so I continued working. Even though this is just a hobby project that might help me test some other hobby projects, I found the API neat enough that I hope it inspires other devs.
Here is the result (not to be confused with a popular package with the same name that's been around for a while as I found out).
Some noteworthy neat features:
- conformance to the Step protocol can be generated by the "GenSteps" plugin from a simple yaml specification that is compatible with the vscode extension Cucumber (Gherkin) Full Support - this means vscode has something to validate your .feature files against
- regex and number of arguments passed to the step handler are automatically compatible
- access to scenario-scoped state via property wrappers
To give you a minimal impression of what state management and accessing arguments looks like, here's how you define scenario-scoped properties:
extension StateContainer {
@ContainerStorage var cucumber : Cucumber?
@ContainerStorage var file : URL?
@ContainerStorage var result : CukeResult?
}
Here's how you define steps:
# groupName is the name of an array into which all
# step definitions here will be dumped.
# if you use the same groupName in different yaml files,
# all steps will go into the same array
groupName: globSteps
steps:
- step: "^a\/an (unimplemented|pending|flawed|implemented) cucumber$"
className: GivenCucumber
arguments:
- name: cukeState
type: CukeState
...
types:
- name: CukeState
kind: enum
cases:
- unimplemented
- pending
- flawed
- implemented
# there's also support for enum types defined in Swift and some basic types like String, Int and Float
And here's how you use it:
// no protocol conformance necessary, this is done
// in a generated file
struct GivenCucumber {
@Scenario(\.cucumber) var cucumber : Cucumber?
// notice the usage of the CukeState enum type
func onRecognize(cukeState: CukeState) async throws {
// you can initialize cucumber for all subsequent steps
// and use it like a normal property
// if you're sure an earlier step set it, use @Required
// this can for example be useful for a when/then pair
}
}