{"id":14560211,"url":"https://github.com/RedMadRobot/catbird","last_synced_at":"2025-09-04T04:31:46.338Z","repository":{"id":35030871,"uuid":"197349641","full_name":"RedMadRobot/catbird","owner":"RedMadRobot","description":"Mock server for UI tests","archived":false,"fork":false,"pushed_at":"2024-09-23T22:38:56.000Z","size":2366,"stargazers_count":52,"open_issues_count":11,"forks_count":9,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-08-31T11:53:05.624Z","etag":null,"topics":["mock-server","swift","swift-nio","swift-package-manager","ui-testing","ui-tests","vapor"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/RedMadRobot.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-07-17T08:36:46.000Z","updated_at":"2025-01-17T06:18:23.000Z","dependencies_parsed_at":"2024-11-15T17:33:42.633Z","dependency_job_id":"6f534b5c-f012-4bab-af4f-cacbbaa08745","html_url":"https://github.com/RedMadRobot/catbird","commit_stats":{"total_commits":107,"total_committers":8,"mean_commits":13.375,"dds":0.3551401869158879,"last_synced_commit":"d57fe3de2df3ad390494083d4cc9ff183cd6d1b7"},"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/RedMadRobot/catbird","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RedMadRobot%2Fcatbird","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RedMadRobot%2Fcatbird/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RedMadRobot%2Fcatbird/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RedMadRobot%2Fcatbird/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/RedMadRobot","download_url":"https://codeload.github.com/RedMadRobot/catbird/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RedMadRobot%2Fcatbird/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273550649,"owners_count":25125543,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-04T02:00:08.968Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["mock-server","swift","swift-nio","swift-package-manager","ui-testing","ui-tests","vapor"],"created_at":"2024-09-07T00:00:25.593Z","updated_at":"2025-09-04T04:31:45.945Z","avatar_url":"https://github.com/RedMadRobot.png","language":"Swift","funding_links":[],"categories":["Mobile"],"sub_categories":["iOS"],"readme":"![](Resources/header.svg)\n# Catbird\n\n[![Test](https://github.com/RedMadRobot/catbird/actions/workflows/test.yml/badge.svg)](https://github.com/RedMadRobot/catbird/actions/workflows/test.yml)\n[![GitHub license](https://img.shields.io/badge/license-MIT-lightgrey.svg)](https://github.com/RedMadRobot/Catbird/blob/master/LICENSE)\n[![CocoaPods Compatible](https://img.shields.io/cocoapods/v/Catbird.svg)](https://cocoapods.org/pods/Catbird)\n[![Platform](https://img.shields.io/cocoapods/p/Catbird.svg?style=flat)](https://cocoapods.org/pods/Catbird)\n[![SPM compatible](https://img.shields.io/badge/spm-compatible-brightgreen.svg?style=flat)](https://swift.org/package-manager)\n\n![](Resources/logo.png)\n\n## Features\n\n- Dynamic mock server\n- Static file mock server\n- Proxy server with writing response files\n\n## Installation\n\nTo use Catbird in UI-tests you must have Catbird server and Catbird API code which allows you to communicate with the server.\n\n| Type | Server | API code |\n| ---- | ------ | -------- |\n| Manual    | ✅ | ✅ |\n| Homebrew  | ✅ | 🚫 |\n| SPM       | 🚫 | ✅ |\n| CocoaPods | ✅ | ✅ |\n\n### Manual\n\nDownload [catbird.zip](https://github.com/RedMadRobot/catbird/releases/latest/download/catbird.zip) archive from the [latest release](https://github.com/RedMadRobot/catbird/releases/latest) page.\n\n### Using [Homebrew](http://brew.sh/):\n\nRun the following command:\n\n```\nbrew install RedMadRobot/formulae/catbird\n```\n\n### Using [SPM](https://www.swift.org/package-manager/):\n\nIf you have an Xcode project, open it and add Catbird Package using the following URL:\n\n`https://github.com/RedMadRobot/catbird.git`\n\n### Using [CocoaPods](https://cocoapods.org):\n\nAdd `Catbird` to UI tests target.\n\n```ruby\ntarget 'App' do\n  use_frameworks!\n\n  target 'AppUITests' do\n    inherit! :search_paths\n\n    pod 'Catbird'\n  end\nend\n```\n\n## Setup in Xcode project\n\n- Open `Schema/Edit scheme...`\n- Select Test action\n- Select `Pre-Actions`\n  - Add `New Run Script action`\n  - Provide build setting from `\u003cYOUR_APP_TARGET\u003e`\n  - `${PODS_ROOT}/Catbird/start.sh`\n- Select `Post-Actions`\n  - Add `New Run Script action`\n  - Provide build setting from `\u003cYOUR_APP_TARGET\u003e`\n  - `${PODS_ROOT}/Catbird/stop.sh`\n\n## Usage\n\n```swift\nimport XCTest\nimport Catbird\n\nenum LoginMock: CatbirdMockConvertible {\n    case success\n    case blockedUserError\n\n    var pattern: RequestPattern {\n        RequestPattern(method: .POST, url: URL(string: \"/login\")!)\n    }\n\n    var response: ResponseMock {\n        switch self {\n        case .success:\n            let json: [String: Any] = [\n                \"data\": [\n                    \"access_token\": \"abc\",\n                    \"refresh_token\": \"xyz\",\n                    \"expired_in\": \"123\",\n                ]\n            ]\n            return ResponseMock(\n                status: 200,\n                headers: [\"Content-Type\": \"application/json\"],\n                body: try! JSONSerialization.data(withJSONObject: json))\n\n        case .blockedUserError:\n            let json: [String: Any] = [\n                \"error\": [\n                    \"code\": \"user_blocked\",\n                    \"message\": \"user blocked\"\n                ]\n            ]\n            return ResponseMock(\n                status: 400,\n                headers: [\"Content-Type\": \"application/json\"],\n                body: try! JSONSerialization.data(withJSONObject: json))\n        }\n    }\n}\n\nfinal class LoginUITests: XCTestCase {\n\n    private let catbird = Catbird()\n    private var app: XCUIApplication!\n\n    override func setUp() {\n        super.setUp()\n        continueAfterFailure = false\n        app = XCUIApplication()\n\n        // Base URL in app `UserDefaults.standard.url(forKey: \"url_key\")`\n        app.launchArguments = [\"-url_key\", catbird.url.absoluteString]\n        app.launch()\n    }\n\n    override func tearDown() {\n        XCTAssertNoThrow(try catbird.send(.removeAll), \"Remove all requests\")\n        super.tearDown()\n    }\n\n    func testLogin() {\n        XCTAssertNoThrow(try catbird.send(.add(LoginMock.success)))\n\n        app.textFields[\"login\"].tap()\n        app.textFields[\"login\"].typeText(\"john@example.com\")\n        app.secureTextFields[\"password\"].tap()\n        app.secureTextFields[\"password\"].typeText(\"qwerty\")\n        app.buttons[\"Done\"].tap()\n\n        XCTAssert(app.staticTexts[\"Main Screen\"].waitForExistence(timeout: 3))\n    }\n\n    func testBlockedUserError() {\n        XCTAssertNoThrow(try catbird.send(.add(LoginMock.blockedUserError)))\n\n        app.textFields[\"login\"].tap()\n        app.textFields[\"login\"].typeText(\"peter@example.com\")\n        app.secureTextFields[\"password\"].tap()\n        app.secureTextFields[\"password\"].typeText(\"burger\")\n        app.buttons[\"Done\"].tap()\n\n        XCTAssert(app.alerts[\"Error\"].waitForExistence(timeout: 3))\n    }\n}\n```\n\n### Request patterns\n\nYou 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.\n\nThree types of patterns can be used:\n\n- `equal` - the request value must be exactly the same as the pattern value,\n- `wildcard` - the request value match with the wildcard pattern (see below),\n- `regexp` - the request value match with the regular expression pattern.\n\n##### Note:\nIf you want to apply a wildcard pattern for the url query parameters, don't forget escape `?` symbol after domain or path.\n\n```swift\nPattern.wildcard(\"http://example.com\\?query=*\")\n```\n\n### Wildcard pattern\n\n\"Wildcards\" are the patterns you type when you do stuff like `ls *.js` on the command line, or put `build/*` in a `.gitignore` file.\n\nIn our implementation any wildcard pattern translates to regular expression and applies matching with URL or header string.\n\nThe following characters have special magic meaning when used in a pattern:\n\n- `*` matches 0 or more characters\n- `?` matches 1 character\n- `[a-z]` matches a range of characters, similar to a RegExp range.\n- `{bar,baz}` matches one of the substitution listed in braces. For example pattern  `foo{bar,baz}` matches strings `foobar` or `foobaz`\n\nYou can escape special characters with backslash `\\`.\n\nNegation in groups is not supported.\n\n\n## Example project\n\n```bash\n$ cd Example/CatbirdX\n$ bundle exec pod install\n$ xed .\n```\n\n## Environment variables\n\n- `CATBIRD_MOCKS_DIR` — Directory where static mocks are located.\n- `CATBIRD_RECORD_MODE` —  set this variable to `1` so that the application starts recording HTTP responses along the path set in `CATBIRD_MOCKS_DIR`. Default `0`.\n- `CATBIRD_REDIRECT_URL` — set this url to forward direct requests to catbird. By default, nil. If the recording mode is not enabled, then first the responses will be searched in the mocks and only if nothing is found, the request will be forwarded.\n- `CATBIRD_PROXY_ENABLED` — set this variable to `1` to forward proxy requests to catbird. By default, `0`. If the recording mode is not enabled, then first the responses will be searched in the mocks and only if nothing is found, the request will be proxied.\n\n\u003e Catbird supports proxying only HTTP requests. HTTPS requests are not supported!\n\n### Redirect example\n\nRun catbird with `CATBIRD_REDIRECT_URL`.\n\n```bash\nCATBIRD_REDIRECT_URL=https://api.github.com ./catbird\n```\n\nAll direct requests will be forwarded to `CATBIRD_REDIRECT_URL`.\n\n```bash\ncurl http://127.0.0.1:8080/zen\n```\n\nThe response will be returned as to the request https://api.github.com/zen\n\n### Proxy example\n\nRun catbird with `CATBIRD_PROXY_ENABLED=1`.\n\n```bash\nCATBIRD_PROXY_ENABLED=1 ./catbird\n```\n\nBy enabling this mode, the catbird will be running as a local http proxy server. \nYou can configure your http client to use this proxy, and all requests will be proxied thought the catbird and redirected to the real host.\nIt might be helpful if you don't want to change the base url of your requests.\n\n```bash\ncurl http://api.github.com/zen --proxy http://127.0.0.1:8080\n```\n\n## Logs\n\nLogs can be viewed in the `Console.app` with subsystem `com.redmadrobot.catbird`\n\nDon't forget to include the message in the action menu\n\n- [x] Include Info Messages\n- [x] Include Debug Messages\n\nWithout this, only error messages will be visible\n\n## Web\n\nYou can view a list of all intercepted requests on the page http://127.0.0.1:8080/catbird\n\n## Parallel testing\n\nFor parallel testing you need to fulfill several conditions.\n\n- Create a `Catbird` instance for each test case or test method with a unique `parallelId` identifier.\n- Pass `parallelId` to the application.\n- Add `parallelId` as **X-Catbird-Parallel-Id** to each request header in application.\n\n```swift\nfinal class LoginUITests: XCTestCase {\n\n    private let catbird = Catbird(parallelId: UUID().uuidString)\n    private var app: XCUIApplication!\n\n    override func setUp() {\n        super.setUp()\n        continueAfterFailure = false\n        app = XCUIApplication()\n\n        app.launchArguments = [\n            // Base URL in app `UserDefaults.standard.url(forKey: \"url_key\")`\n            \"-url_key\", catbird.url.absoluteString,\n\n            // `parallelId` in app `UserDefaults.standard.url(forKey: \"parallelId\")`\n            \"-parallelId\", catbird.parallelId!\n        ]\n        app.launch()\n    }\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FRedMadRobot%2Fcatbird","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FRedMadRobot%2Fcatbird","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FRedMadRobot%2Fcatbird/lists"}