/catbird

Mock server for UI tests

Primary LanguageSwiftMIT LicenseMIT

Catbird

Build Status GitHub license CocoaPods Compatible Platform SPM compatible

Features

  • Dynamic mock server
  • Static file mock server
  • Proxy server with writing response files

Installation

Cocoapods

Add Catbird to UI tests target.

target 'App' do
  use_frameworks!

  target 'AppUITests' do
    inherit! :search_paths

    pod 'Catbird'
  end
end

Setup in Xcode project

  • 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
  • Select Post-Actions
    • Add New Run Script action
    • Provide build setting from <YOUR_APP_TARGET>
    • ${PODS_ROOT}/Catbird/stop.sh

Usage

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("john@example.com")
        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("peter@example.com")
        app.secureTextFields["password"].tap()
        app.secureTextFields["password"].typeText("burger")
        app.buttons["Done"].tap()

        XCTAssert(app.alerts["Error"].waitForExistence(timeout: 3))
    }
}

Request patterns

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.
Note:

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=*")

Wildcard pattern

"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 pattern foo{bar,baz} matches strings foobar or foobaz

You can escape special characters with backslash \.

Negation in groups is not supported.

Example project

$ cd Example/CatbirdX
$ bundle exec pod install
$ xed .

Environment variables

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

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

Web

You can view a list of all intercepted requests on the page http://127.0.0.1:8080/catbird