Skip to content

Commit

Permalink
[main] - Docs on UIUTest and step matching - TT
Browse files Browse the repository at this point in the history
  • Loading branch information
Tyler-Keith-Thompson committed Sep 5, 2022
1 parent 5dcac2d commit 3b1300b
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 0 deletions.
70 changes: 70 additions & 0 deletions Sources/CucumberSwift/CucumberSwift.docc/CucumberSwift+UIUTest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# CucumberSwift+UIUTest

A colleague of mine wrote [UIUTest](https://github.com/nallick/UIUTest) which allows for UI testing in a unit testing bundle. It drastically speeds up UI tests but gives a lot of the same functionality, like making sure elements are not covered, able to be tapped etc...

So in many of our projects that have to function at a large scale and have quite a bit of gherkin we combined CucumberSwift and UIUTest for some really cool results!

### SETUP:

#### Podfile:
```ruby
def shared_pods
#production pods go here
end

target 'App' do
use_frameworks!

shared_pods
end

target 'AppUnitTests' do
use_frameworks!

shared_pods
end

target 'AppCucumberTests' do
use_frameworks!
inherit! :search_paths

shared_pods
pod 'UIUTest'
pod 'CucumberSwift'
end
```

#### XCode Setup
When adding your `AppCucumberTests` target make sure to add it as a `Unit Testing Bundle`

There's always a weird tendency for people to lowercase the name of their features folder so the plist should contain `FeaturesPath` with the relative path to the folder (e.g. `specs/features`)

#### Magic Happens Here
The `AppCucumberTests.swift` file looks something like this
```swift
import XCTest
import UIUTest
import CucumberSwift

@testable import App

extension Cucumber : StepImplementation {
public var bundle: Bundle {
class ThisBundle { }
return Bundle(for: ThisBundle.self)
}

public func setupSteps() {
BeforeScenario { _ in
UIViewController.initializeTestable()
}
AfterScenario { _ in
UIViewController.flushPendingTestArtifacts()
}
setupEnrollmentTests() //this is a method that lives in a different file with the Given(regex, closure) syntax. These setup methods usually contain all the implementation needed for a .feature file
}
}
```

#### What was the result?
We saw a huge performance increase using this over XCUITest but got the same value for those tests. At the time we went from about 20 minutes of UI test execution time to about 2 minutes. We chose not to mock our network calls which is why these tests didn't run in a matter of seconds.
2 changes: 2 additions & 0 deletions Sources/CucumberSwift/CucumberSwift.docc/CucumberSwift.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@ CucumberSwift is a lightweight Cucumber implementation for iOS, tvOS, and macOS.

### Discussions
- <doc:Tutorial-Table-of-Contents>
- <doc:Matching-Steps>
- <doc:Generating-Reports>
- <doc:Hooks>
- <doc:CucumberSwift+UIUTest>
85 changes: 85 additions & 0 deletions Sources/CucumberSwift/CucumberSwift.docc/Matching-Steps.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Matching Steps

Gherkin defined in `.feature` files can be matched in several ways. All matching is done using global functions that align with gherkin keywords. For example, ``Given``, ``When``, and ``Then`` functions. These functions are also localized, so if you'd rather use spanish you can use ``ES_Dado``.

## Matching with Cucumber Expressions
Cucumber has [its own expressions](https://github.com/cucumber/cucumber-expressions#readme) that CucumberSwift supports. These are an alternative to regular expressions that are a little more readable. They aren't nearly as powerful when it comes to precise matching, but they can be extended with regular expressions and can very likely meet the majority of use-cases. Normally, Cucumber implementations use expressions by default in languages that have regular expression literals. However, because CucumberSwift was created long before Swift supported regex literals they are *not* the default.

Imagine the following step:
```gherkin
Given there are 3 flights from lax.
```

We could match it in CucumberSwift like this:
```swift
Given("there is/are/were {int} flight(s) from {airport}." as CucumberExpression) { match, _ in
XCTAssertEqual(match[\.int, index: 0], 3)
XCTAssertEqual(try match.first(\.int), 3)
XCTAssertEqual(try match.last(\.int), 3)
XCTAssertEqual(try match.allParameters(\.int), [3])
XCTAssertIdentical(match[\.airport, index: 0], Airport.lax)
XCTAssertIdentical(try match.first(\.airport), Airport.lax)
XCTAssertIdentical(try match.last(\.airport), Airport.lax)
XCTAssertEqual(try match.allParameters(\.airport).count, 1)
XCTAssertIdentical(try match.allParameters(\.airport).first, Airport.lax)
}
```

Notice that `{airport}` is a custom parameter. It's type-safe which is great, but how do we create our own custom parameters?

```swift
// Just an example of airports, this is simply your model.
class Airport {
static let lax = Airport()
}

// This extension must contain all custom parameters you want to use.
extension CucumberExpression: CustomParameters {
public static var additionalParameters: [CucumberSwiftExpressions.AnyParameter] {
[
AirportParameter().eraseToAnyParameter()
]
}
}

// The airport parameter
struct AirportParameter: Parameter {
enum ParameterError: Error {
case airportNotFound
}

// Globally unique name for this parameter
static let name = "airport"

// A regular expression to use to match this parameter.
let regexMatch = #"[A-Z]{3}"#

// A transform from the matched string to whatever type you want
func convert(input: String) throws -> Airport {
switch input.lowercased() {
case "lax": return Airport.lax
default:
throw ParameterError.airportNotFound
}
}
}

// A convenience property to use that keypath syntax you saw in the previous example.
extension Match {
var airport: AirportParameter {
AirportParameter()
}
}
```

## Matching with Regular Expressions
Regular expressions are a very powerful tool. If you can support regex literals in your tests, they are by far the preferable method to match with.

Here's a trivial example:
```swift
When(/^some (\w+) by the actor$/.ignoresCase()) { match, _ in
XCTAssertEqual(match.1, "action")
}
```

> NOTE: You can use regex builders in Swift to transform into concrete types. It's a little verbose, but is supported by CucumberSwift.

0 comments on commit 3b1300b

Please sign in to comment.