- Dynamic mock server
- Static file mock server
- Proxy server with writing response files
To use Catbird in UI-tests you must have Catbird server and Catbird API code which allows you to communicate with the server.
Type | Server | API code |
---|---|---|
Manual | ✅ | ✅ |
Homebrew | ✅ | 🚫 |
SPM | 🚫 | ✅ |
CocoaPods | ✅ | ✅ |
Download catbird.zip archive from the latest release page.
Using Homebrew:
Run the following command:
brew install RedMadRobot/formulae/catbird
Using SPM:
If you have an Xcode project, open it and add Catbird Package using the following URL:
https://github.com/RedMadRobot/catbird.git
Using CocoaPods:
Add Catbird
to UI tests target.
target 'App' do
use_frameworks!
target 'AppUITests' do
inherit! :search_paths
pod 'Catbird'
end
end
- Open
Schema/Edit scheme...
- Select Test action
- Select
Pre-Actions
- Add
New Run Script action
- Provide build setting from
<YOUR_APP_TARGET>
${PODS_ROOT}/Catbird/start.sh
- Add
- Select
Post-Actions
- Add
New Run Script action
- Provide build setting from
<YOUR_APP_TARGET>
${PODS_ROOT}/Catbird/stop.sh
- Add
import XCTest
import Catbird
enum LoginMock: CatbirdMockConvertible {
case success
case blockedUserError
var pattern: RequestPattern {
RequestPattern(method: .POST, url: URL(string: "/login")!)
}
var response: ResponseMock {
switch self {
case .success:
let json: [String: Any] = [
"data": [
"access_token": "abc",
"refresh_token": "xyz",
"expired_in": "123",
]
]
return ResponseMock(
status: 200,
headers: ["Content-Type": "application/json"],
body: try! JSONSerialization.data(withJSONObject: json))
case .blockedUserError:
let json: [String: Any] = [
"error": [
"code": "user_blocked",
"message": "user blocked"
]
]
return ResponseMock(
status: 400,
headers: ["Content-Type": "application/json"],
body: try! JSONSerialization.data(withJSONObject: json))
}
}
}
final class LoginUITests: XCTestCase {
private let catbird = Catbird()
private var app: XCUIApplication!
override func setUp() {
super.setUp()
continueAfterFailure = false
app = XCUIApplication()
// Base URL in app `UserDefaults.standard.url(forKey: "url_key")`
app.launchArguments = ["-url_key", catbird.url.absoluteString]
app.launch()
}
override func tearDown() {
XCTAssertNoThrow(try catbird.send(.removeAll), "Remove all requests")
super.tearDown()
}
func testLogin() {
XCTAssertNoThrow(try catbird.send(.add(LoginMock.success)))
app.textFields["login"].tap()
app.textFields["login"].typeText("[email protected]")
app.secureTextFields["password"].tap()
app.secureTextFields["password"].typeText("qwerty")
app.buttons["Done"].tap()
XCTAssert(app.staticTexts["Main Screen"].waitForExistence(timeout: 3))
}
func testBlockedUserError() {
XCTAssertNoThrow(try catbird.send(.add(LoginMock.blockedUserError)))
app.textFields["login"].tap()
app.textFields["login"].typeText("[email protected]")
app.secureTextFields["password"].tap()
app.secureTextFields["password"].typeText("burger")
app.buttons["Done"].tap()
XCTAssert(app.alerts["Error"].waitForExistence(timeout: 3))
}
}
You can specify a pattern for catch http requests and make a response with mock data. Pattern matching applied for URL and http headers in the request. See RequestPattern
struct.
Three types of patterns can be used:
equal
- the request value must be exactly the same as the pattern value,wildcard
- the request value match with the wildcard pattern (see below),regexp
- the request value match with the regular expression pattern.
If you want to apply a wildcard pattern for the url query parameters, don't forget escape ?
symbol after domain or path.
Pattern.wildcard("http://example.com\?query=*")
"Wildcards" are the patterns you type when you do stuff like ls *.js
on the command line, or put build/*
in a .gitignore
file.
In our implementation any wildcard pattern translates to regular expression and applies matching with URL or header string.
The following characters have special magic meaning when used in a pattern:
*
matches 0 or more characters?
matches 1 character[a-z]
matches a range of characters, similar to a RegExp range.{bar,baz}
matches one of the substitution listed in braces. For example patternfoo{bar,baz}
matches stringsfoobar
orfoobaz
You can escape special characters with backslash \
.
Negation in groups is not supported.
$ cd Example/CatbirdX
$ bundle exec pod install
$ xed .
CATBIRD_MOCKS_DIR
— Directory where static mocks are located.
CATBIRD_PROXY_URL
— If you specify this URL Catbird will run in write mode. In this mode, requests to Catbird will be redirected to the CATBIRD_PROXY_URL
. Upon receipt of response from the server it will be written to the CATBIRD_MOCKS_DIR
directory.
Logs can be viewed in the Console.app
with subsystem com.redmadrobot.catbird
Don't forget to include the message in the action menu
- Include Info Messages
- Include Debug Messages
Without this, only error messages will be visible
You can view a list of all intercepted requests on the page http://127.0.0.1:8080/catbird
For parallel testing you need to fulfill several conditions.
- Create a
Catbird
instance for each test case or test method with a uniqueparallelId
identifier. - Pass
parallelId
to the application. - Add
parallelId
as X-Catbird-Parallel-Id to each request header in application.
final class LoginUITests: XCTestCase {
private let catbird = Catbird(parallelId: UUID().uuidString)
private var app: XCUIApplication!
override func setUp() {
super.setUp()
continueAfterFailure = false
app = XCUIApplication()
app.launchArguments = [
// Base URL in app `UserDefaults.standard.url(forKey: "url_key")`
"-url_key", catbird.url.absoluteString,
// `parallelId` in app `UserDefaults.standard.url(forKey: "parallelId")`
"-parallelId", catbird.parallelId!
]
app.launch()
}
}