{"id":32110244,"url":"https://github.com/valnoc/mirage","last_synced_at":"2025-10-20T13:59:55.155Z","repository":{"id":62447827,"uuid":"102219064","full_name":"valnoc/Mirage","owner":"valnoc","description":"Mirage is a mocking library for swift projects. The recommended way is to use https://github.com/valnoc/FataMorgana for Mirage mocks generation to avoid writing them manually.","archived":false,"fork":false,"pushed_at":"2020-01-24T19:54:14.000Z","size":148,"stargazers_count":8,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-20T13:59:44.923Z","etag":null,"topics":["mock","mocking","mocks","stub","stubbing","stubs","swift","tdd","unit-testing","unittest","unittesting"],"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/valnoc.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}},"created_at":"2017-09-02T19:31:28.000Z","updated_at":"2020-11-13T03:08:15.000Z","dependencies_parsed_at":"2022-11-01T23:02:17.705Z","dependency_job_id":null,"html_url":"https://github.com/valnoc/Mirage","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/valnoc/Mirage","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/valnoc%2FMirage","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/valnoc%2FMirage/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/valnoc%2FMirage/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/valnoc%2FMirage/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/valnoc","download_url":"https://codeload.github.com/valnoc/Mirage/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/valnoc%2FMirage/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":280102718,"owners_count":26272391,"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-10-20T02:00:06.978Z","response_time":62,"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","mocking","mocks","stub","stubbing","stubs","swift","tdd","unit-testing","unittest","unittesting"],"created_at":"2025-10-20T13:59:51.812Z","updated_at":"2025-10-20T13:59:55.146Z","avatar_url":"https://github.com/valnoc.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Mirage\n[![License](https://img.shields.io/github/license/valnoc/Mirage.svg)](https://github.com/valnoc/Mirage/blob/master/LICENSE) [![GitHub issues](https://img.shields.io/github/issues-raw/valnoc/Mirage.svg)](https://github.com/valnoc/Mirage/issues) \n\n[![Cocoapods release](https://img.shields.io/cocoapods/v/Mirage.svg)]() [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![GitHub release](https://img.shields.io/github/release/valnoc/Mirage.svg)](https://github.com/valnoc/Mirage/releases) \n\nMirage is a mocking library for swift projects. \nThe recommended way is to use [Fata Morgana](https://github.com/valnoc/FataMorgana) for mocks generation to avoid writing them manually.\n\n- [Features](#features)\n- [Installation](#installation)\n  - [Carthage](#carthage)\n  - [Cocoapods](#cocoapods)\n  - [Source files](#source-files)\n- [Usage](#usage)\n  - [Mocks](#mocks)\n  - [Stubs](#stubs)\n  - [Partial mocks](#partial-mocks)\n  - [Verify call times](#verify-call-times)\n  - [Call Args](#call-args)\n- [Migration Guide](#migration-guide)\n- [License](#license)\n\n## Features\nUsing Mirage you can:\n- create mocks, stubs, partial mocks\n- verify `func` call times\n- get call arguments history\n\n---\n## Installation\nRequires Swift 4.2+\n\n#### Carthage\nAdd this line into your Cartfile, run `carthage update --platform iOS` and link binary to the target as you always do it)\n```ruby\ngithub \"valnoc/Mirage\" ~\u003e 2.0\n```\n\n#### Cocoapods\nAdd this line into your Podfile under a test target and run `pod update`\n```ruby\npod 'Mirage', '~\u003e 2.0'\n```\n\nPodfile example\n```ruby\ntarget 'MainTarget' do\n  ...\n  target 'TestTarget' do\n    inherit! :search_paths\n    pod 'Mirage'\n  end\nend\n```\n#### Source files\nCopy /Mirage folder into your test target.\n\n---\n## Usage\nCheck Example project for details.\nTry [Fata Morgana](https://github.com/valnoc/FataMorgana) for mocks generation.\n\n### Mocks\nA Mock is an object which mimics behaviour of a real object and records functions' calls. You can create `class` mocks and `protocol` mocks in the same way.\n\n\u003e The **first version of Mirage** provided the instruments to create a mock for a whole class or a protocol. A mock could be easily created manually but the usage was not so good - you had to cast args to there types every time you call `args(of:)` and the stubs returned `Any`. Since **Mirage 2** funcs are mocked individually. \n\nAll mocks and stubs are generics. They use `TArgs` and `TReturn` types.\n\nIf a func has one argument `TArgs` should be of its type. But if it has several arguments you should create a struct (or class) as a container of this args.\n\n`TReturn` represents func's return type.\n\n#### Mock Example\nLet's create a mock for this class\n\n```swift\nclass Calculator {\n    func sum(_ left: Int, _ right: Int) -\u003e Int {\n        return left + right\n    }\n}\n```\n\n##### Full variant\n1. Create a new Mock class inhereted from the original \n```swift\nimport Mirage\n\nclass MockCalculator: Calculator {\n```\n2. `func sum` has 2 args `left` and `right` so let's create a nested class (or a struct) to contain them\n```swift\n    class SumArgs {\n        let left: Int\n        let right: Int\n        \n        init(left: Int, right: Int) {\n            self.left = left\n            self.right = right\n        }\n    }\n```\n3. Add this `func` to call real implementation of this function\n```swift\n    fileprivate func super_sum(_ args: SumArgs) -\u003e Int {\n        return super.sum(args.left, args.right)\n    }\n```\n4. Add `FuncCallHandler`. This is the core of func mocking.\n```swift\n    lazy var mock_sum = FuncCallHandler\u003cSumArgs, Int\u003e(returnValue: anyInt(),\n                                                      callRealFunc: { [weak self] (args) -\u003e Int in\n                                                        guard let __self = self else { return anyInt() }\n                                                        return __self.super_sum(args)\n    })\n```    \n5. `override` the original func and call `mock_sum` to handle func call\n```swift\n    override func sum(_ left: Int, _ right: Int) -\u003e Int {\n        let args = SumArgs(left: left, right: right)\n        return mock_sum.handle(args)\n    }\n```\n\nThis is it)\n```swift\nclass MockCalculator: Calculator {\n    //MARK: - sum\n    class SumArgs {\n        let left: Int\n        let right: Int\n        \n        init(left: Int, right: Int) {\n            self.left = left\n            self.right = right\n        }\n    }\n    fileprivate func super_sum(_ args: SumArgs) -\u003e Int {\n        return super.sum(args.left, args.right)\n    }\n    lazy var mock_sum = FuncCallHandler\u003cSumArgs, Int\u003e(returnValue: anyInt(),\n                                                      callRealFunc: { [weak self] (args) -\u003e Int in\n                                                        guard let __self = self else { return anyInt() }\n                                                        return __self.super_sum(args)\n    })\n    override func sum(_ left: Int, _ right: Int) -\u003e Int {\n        let args = SumArgs(left: left, right: right)\n        return mock_sum.handle(args)\n    }\n}\n```\n\n##### Short variant\n`super_sum` and `callRealFunc` can be skipped if you are not going to use it.\nYou can also use a struct and get a generated `init`.\n```swift\nclass MockCalculator: Calculator {\n    //MARK: - sum\n    struct SumArgs {\n        let left: Int\n        let right: Int\n    }\n    lazy var mock_sum = FuncCallHandler\u003cSumArgs, Int\u003e(returnValue: anyInt())\n    override func sum(_ left: Int, _ right: Int) -\u003e Int {\n        let args = SumArgs(left: left, right: right)\n        return mock_sum.handle(args)\n    }\n}\n```\n\n### Stubs\nFunction stubbing allows to change the behavour of a function according to testing needs.\nTo create a stub, call mock function `whenCalled()`. \n\nThen call one of the following functions:\n* `thenReturn(_ result: TReturn)` to return the exact value as a result\n* `thenDo(_ closure: @escaping Action)` to execute closure instead of called function\n* `thenCallRealFunc()` to call real implementation of this function\n\nThis `thenSmth` calls can be chained to return one result for the first call and another one for the next calls.\n\n```swift\ncalculator.mock_sum.whenCalled().thenReturn(number)\n\nrandomNumberGenerator.mock_makeInt.whenCalled()\n    .thenReturn(5)\n    .thenReturn(10)\n```\n\n### Partial mocks\nA Partial mock is the same thing as a mock but it automatically calls real implementations of its functions.\nThere are discussions whether partial mock is a pattern or an anti-pattern, whether you therefore should use them or not. \n\nMirage allows you to create a partial mock with one line of code. It's up to you - to use or not to use.\nTo create a partial mock of a func, add `isPartial: true` to a `FuncCallHandler`\n```swift\nlazy var mock_performMainOperation = FuncCallHandler\u003cVoid, Void\u003e(returnValue: (),\n                                                                 isPartial: true,\n                                                                 callRealFunc: { [weak self] (args) -\u003e Void in\n                                                                    guard let __self = self else { return () }\n                                                                    return __self.super_performMainOperation()\n})\n```\n\n### Verify call times\nYou can call `verify(called:)` on any `FuncCallHandler` to check the number of times this func was called\n```swift\n- never: callTimes == 0\n- once: callTimes == 1\n- times: callTimes == *value*\n- atLeast: callTimes \u003e= *value*\n- atMost: callTimes \u003c= *value*\n```\n\n`verify(called:)` throws `CallTimesRuleIsBroken` if actual call times do not match the given rule.\n\nUse `XCTAssertNoThrow(try ...)` with `verify` call\n```swift\nXCTAssertNoThrow(try randomNumberGenerator.mock_makeInt.verify(called: .times(2)))\nXCTAssertNoThrow(try calculator.mock_sum.verify(called: .once))\n\nXCTAssertNoThrow(try logger.mock_logPositiveResult.verify(called: .once))\nXCTAssertNoThrow(try logger.mock_logNegativeResult.verify(called: .never))\n```\n\n### Call Args\nYou can get arguments of any call from history using `args() -\u003e TArgs?` or `args(callTime: Int) -\u003e TArgs?` functions. It returns *an array of arguments* for this call or *nil* if no call with given *callTime* was registered. \n\nSo the best pratice is to use guard and XCTFail around `argsOf()` if you expect this args to exist.\n```swift\n// then\nguard let args = calculator.mock_sum.args() else { XCTFail(); return }\nXCTAssert(args.left == 5)\nXCTAssert(args.right == 10)\n```\n\n## Migration Guide\nIn order to migrate a project from the first version of Mirage framework to the second one, the framework has been renamed as `Mirage2`. It allows to use both the first and the second versions in the same project and to migrate file by file.\n\nMigration from *Mirage 1* to *Mirage 2* consists of several steps.\n\n1. Rewrite mocks\n\nIt is very easy to migrate mocks with new version of [Fata Morgana](https://github.com/valnoc/FataMorgana)\n\n2. Use find\u0026replace feature to change `Once()` to `.once`, etc\n\n3. Use find\u0026replace feature along with regexps to change `verify` to `when` calls\nI'll give regexps here later\n\n4. Remove args casts after `args()` calls\n\n## License\nMirage is available under MIT License.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvalnoc%2Fmirage","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvalnoc%2Fmirage","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvalnoc%2Fmirage/lists"}