{"id":19631807,"url":"https://github.com/jakehawken/poseur","last_synced_at":"2026-06-06T21:32:13.404Z","repository":{"id":136742870,"uuid":"97769999","full_name":"jakehawken/Poseur","owner":"jakehawken","description":"A class and protocol for spying and stubbing in Swift.","archived":false,"fork":false,"pushed_at":"2021-07-16T20:20:51.000Z","size":64,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-02-26T21:14:19.311Z","etag":null,"topics":["mocking","stubbing","stubbing-framework","swift"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jakehawken.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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":"2017-07-19T23:31:34.000Z","updated_at":"2022-05-03T10:42:10.000Z","dependencies_parsed_at":null,"dependency_job_id":"ec44364d-0463-4ecf-8388-1cff6b6aae35","html_url":"https://github.com/jakehawken/Poseur","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/jakehawken/Poseur","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jakehawken%2FPoseur","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jakehawken%2FPoseur/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jakehawken%2FPoseur/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jakehawken%2FPoseur/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jakehawken","download_url":"https://codeload.github.com/jakehawken/Poseur/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jakehawken%2FPoseur/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":285519539,"owners_count":27185526,"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-11-20T02:00:05.334Z","response_time":54,"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":["mocking","stubbing","stubbing-framework","swift"],"created_at":"2024-11-11T12:11:35.393Z","updated_at":"2025-11-20T22:04:01.027Z","avatar_url":"https://github.com/jakehawken.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Poseur\n\nWhen writing unit tests, we want to test specific units of code, decoupled from the actual state of our application. To do this, we simulate the other units of code with which the test subject communicates. Doing this is referred to as putting the subject into a “test harness.”\n\nThese simulated units of code are what we generally call “fakes.” They allow the test code to change the conditions around the subject to replicate known app states/conditions, such as the success or failure of a network call, or a specific state on a helper class. Changing how these fakes respond to being interacted with by the subject is called “stubbing” and recording whether or methods or properties have on the fake have been accessed is called “spying.”\n\nPoseur is a class and protocol for creating test fakes for stubbing/spying in Swift, inspired by the (more fully-featured framework) [Spry](https://github.com/Rivukis/Spry) framework.\n\n## The Cast of Characters\n\nPoseur has two main components:\n\n#### 1. The `Faker\u003cFunction\u003e` object\n\nThis object encapsulates most of the `Fake` functionality. It works as a helper object to manage the spying and stubbing for a given fake. It has a generic *Function* type which allows the user to implement any kind of object to define their method captures as long as that object conforms to `PoseurFunction` which simply is a bundling of the *Equatable* and *Hashable* protocols.\n\n#### 2. The `Fake` protocol\n\nThis protocol is what your fake objects will actually conform to. It's designed to closely mirror the `Faker\u003cFunction\u003e` object, so that in tests you can inspect the fake itself rather than having to inspect its `.faker` property. Since the majority of the protocol is intended to be passthrough to the faker, I have included a protocol extension which does exactly that. And thanks to that, all one needs to do to conform to `Fake` is to define the `Function` type, and implement the `faker` property, which will include that type as its generic. I've even created a convenience extension method to `Function` so that all one need to do to implement that property is add this line: `let faker = Function.faker()`\n\nA bit of advice: When you define your `Function` type, I recommend using an enum, as it gives you a finite set of cases, each corresponding to a given function/method.\n\n# How to Use\n\nImagine, if you will, that you have a class called `Dog`:\n\n```swift\nclass Dog {\n\n    private var stomach = [DogFood]()\n    private var shouldPoop = false\n    private static let barks = [\"woof!\", \"bork!\", \"yip!\"]\n\n    func bark() -\u003e String {\n        return Dog.barks.randomElement() ?? \"glorf?\"\n    }\n\n    func eat(food: DogFood) {\n        stomach.append(food)\n    }\n\n    func digest() -\u003e String? {\n        if shouldPoop {\n            let firstEaten = stomach.removeFirst()\n            return firstEaten.digested\n        }\n        shouldPoop = !shouldPoop\n        return nil\n    }\n    \n    func rollOntoTummy(getARub: Bool) -\u003e String {\n        if getARub {\n            return \"Panting sounds...\"\n        }\n        return \"Whimpering\"\n    }\n    \n    func shouldFetch(_ item: FetchableItem, for familyMember: FamilyMember) -\u003e Bool {\n        switch (item, familyMember) {\n        case (.slippers, .kid):\n            return false\n        default:\n            return true\n        }\n    }\n    \n}\n```\n\n## Conforming to Fake\n\nNow imagine that `Dog` is a depency in a class, `HappyFamily`, that you're testing. As such, you want to generate a fake so that you can control `Dog`'s behavior in your tests. Poseur allows you to create a fake simply by creating a subclass of `Dog` that conforms to the `Fake` protocol, and then overriding its methods. Check it out!\n\n```swift\nclass FakeDog: Dog, Fake {\n    \n    enum Function: String, PoseurFunction {\n        case bark\n        case eat\n        case digest\n        case rollOntoTummy\n        case shouldFetch\n    }\n    \n    lazy var faker = Function.faker()\n\n    //MARK: - overrides\n    \n    override func bark() -\u003e String {\n        return recordAndStub(function: .bark)\n    }\n\n    override func eat(food: DogFood) {\n        recordCall(.eat, arguments: food)\n    }\n\n    override func digest() -\u003e String? {\n        return recordAndStub(function: .digest)\n    }\n    \n    override func rollOntoTummy(getARub: Bool) -\u003e String {\n        return recordAndStub(function: .rollOntoTummy,\n                             arguments: getARub)\n    }\n    \n    override func shouldFetch(_ item: FetchableItem, for familyMember: FamilyMember) -\u003e Bool {\n        return recordAndStub(function: .shouldFetch, arguments: item, familyMember)\n    }\n    \n}\n```\n\nWasn't that easy?!\n\nNotes:\n- In `digest()`, `bark()`, and `rollOntoTummy(getARub:)`, we call `recordAndStub\u003cT\u003e(function:asType:arguments:)` which is a convenience method that calls both `recordCall(_:)` and `stubbedValue(forFunction:asType:arguments:)`. Ultimately allowing us to spy on arguments called \n- Since `eat(food:)` is a void method, and we're not wanting to trigger any special behavior for now, `recordCall(_:)` is all we need.\n\n## The three approaches\n\nNow, when interacting with Fakes, there are three main ways to access their fake-specific functionality.\n\n1. Simple: ignoring the arguments passed to the function\n2. `ArgsCheck`: using a custom block to respond to arguments\n3. Argument List: providing a list of arguments\n\nYou'll see what I mean by these as you read on.\n\n# Spying\n\nLet's start take a `FakeDog`\n\n```swift\nlet fakeDog = FakeDog()\n```\n\nand keep tabs on it!\n\n## 1. Simple\n\nSometimes we just want to know if a function was called at all.\n\n```swift\nfakeDog.eat(food: .canned)\nfakeDog.eat(food: .canned)\nfakeDog.eat(food: .canned)\n\nfakeDog.receivedCall(to: .eat) // returns true\nfakeDog.callCountFor(function: .eat) // returns 3\n```\n\nBoth `receivedCall(to:)` and `callCountFor(function:)` use the \"simple\" approach. That means that these methods are reporting on raw number of calls to this function, rather than calls with a specific set of arguments.\n\n## 2. ArgsCheck\n\nSometimes we want to know what arguments are being passed.\n\n```swift\n_ = fakeDog.rollOntoTummy(getARub: true)\n_ = fakeDog.rollOntoTummy(getARub: false)\n_ = fakeDog.rollOntoTummy(getARub: true)\n\nfakeDog.receivedCall(to: .rollOntoTummy) { (arguments) -\u003e Bool in\n    (arguments[0] as? Bool) == true\n} // returns true\n\n//or, written in a more compact form:\nfakeDog.receivedCall(to: .rollOntoTummy, where: { ($0[0] as? Bool) == true })\n```\n\nThis approach allows us to pass in a custom validation block that validates the arguments.\n\n## 3. Argument List\n\nThis is the \"automagic\" option. Since arguments are passed to Poseur as `[Any?]` arrays, type checking can be very challenging. The default implementation evaluates the arguments in the following priority order:\n\n1. The types are checked, and if the arguments are two diffrent types, it return `false`.\n2. If the argument in the recorded call conforms to `AnyEquatable`, it evaluates equality based on that.\n3. If the arguments are both classes, pointer comparison (`===`) is employed. And finally, if none of that works,\n4. Both arguments are interpolated into strings and the strings are compared to one another.\n\n`AnyEquatable` has one method: `func isEqualTo(_ other: Any?) -\u003e Bool` allowing it to be compared to anything. It has a default implementation if the conforming type is already `Equatable`, which means that all you have to do to get an `Equatable` type to conform to `AnyEquatable` is to tell it to, like so:\n\n```swift\nextension Bool: AnyEquatable {}\n```\n\nPoseur conforms several common types right out of the box, namely: `Bool`, `String`, `Int`, `Float`, `Double`, and `NSNumber`. The more you add to that list, the more automagic the argument list approach will be.\n\nIf nothing else, the argument list is certainly more beautiful looking than the ArgsCheck approach:\n\n```swift\nfakeDog.shouldFetch(.slippers, for: .parent)\nfakeDog.receivedCall(to: .shouldFetch, \n                     withArguments: FetchableItem.slippers, FamilyMember.parent)\n// returns true\nfakeDog.receivedCall(to: .shouldFetch, \n                     withArguments: FetchableItem.ball, FamilyMember.kid)\n// returns false\n```\n\n# Stubbing\n\nSpying on what is being communicated to our dependencies is one half of the harness we put our test subjects into. The other half is controlling the behavior of those dependencies to simulate specific scenarios/states. This is is where stubbing enters the picture. Poseur provies a handful of tools for stubbing your functions, and once again, the three main approaches are employed.\n\n## 1. Simple\n\nAs the name would suggest, using this approach is very simple to do.\n\n```swift\nfakeDog.stub(function: .bark).andReturn(\"Meow\")\n// as you might excted, this will result in \nfakeDog.bark() // returning \"Meow\"\n```\n\nThere's a GOTCHA though! When doing a simple, no arguments stub like this, there is an additional side-effect: It becomes the only stub for that function. Any argument-specific stub is automatically overridden in favor of a simple stub if one exists. The only way to override a simple stub is to add a new simple stub.\n\nYou might have noticed the `.andReturn(_:)` method in the example above. A simple stub returns a `Stubbable` which has the methods `.andReturn(_:)` and `func andDo(_:)`. This gives you options. The former is if you want to return a specific value no matter what. The latter provides the array of arguments that were passed to the function (as an array of `[Any?]`, I'm sorry) in a closure so you can provide any additional logic or side-effects your little heart desires. \n\nFor example:\n\n```swift\nfakeDog.stub(function: .shouldFetch).andDo { (arguments) -\u003e Bool in\n    let fetchableItem = arguments[0] as! FetchableItem\n    let familyMember = arguments[1] as! FamilyMember\n    switch (fetchableItem, familyMember) {\n    case (.slippers, .kid):\n        return true\n    default:\n        return false\n    }\n}\nfakeDog.shouldFetch(.slippers, for: .kid) // returns true\n```\n(I normally advise strongly against the use of force-unwrapped optionals, but in a test case like this, they simulate the actual rigidity of the Swift type system, so I'm ok with it. In unit tests more than anywhere else you want to fail *fast*.)\n\nThe `andDo(_:)` method also enables you to stub a method once and have its execution block key off of state variables that you control and manipulate over the course of a test or test suite. An example of where you might want to do this is a function which wraps a network call. You can simply stub it once, and have what the simulated network call returns, or whether it succeeds or fails, based on state variables. Very handy.\n\n## 2. ArgsCheck\n\nAs usual, this method is the hairiest approach, but the one where you have the most direct control. Control, in this case, over whether or not your stub is invoked or whether `Fake` is going to throw a `fatalError(_:)` message at the console about how you haven't stubbed the method in a way that matches the arguments the call was passed.\n\n```swift\nfakeDog.stub(function: .rollOntoTummy, where: { ($0[0] as? Bool) == false }).andReturn(\"HOWL\")\n\n// or \n\nfakeDog.stub(function: .rollOntoTummy) { (arguments) -\u003e Bool in\n    (arguments[0] as? Bool) == false\n}.andReturn(\"HOWL\")\n```\n\nUnlike the simple stub, an ArgsCheck stub returns an `AndReturnable` which only has the `.andReturn(_:)` method.\n\nWhat this means is that you have two options for dynamically responding to arguments:\n\n1. Supply an args check determining whether or not your stub is invoked and give that stub a single return value. This lets you create a different stub for any number of `ArgsCheck` closures, each with a different return value.\n2. Stub the method universally and respond to the arguments (and/or other variables) dynamically on the way out. This lets you have one consolidated closure that encapsulates all of your return value logic. It also is the clearest and easiest way to add side-effects to the function call.\n\nIt's all up to you! Do what makes the most sense for you and works best for your tests.\n\n## 3. Argument list\n\nThe automagic option returns! As always, this is the prettier and easier to read of the two argument-specific stubbing methods.\n\n```swift\nsubject.stub(function: .shouldFetch, withArguments: FetchableItem.slippers, FamilyMember.kid).andReturn(true)\nsubject.stub(function: .shouldFetch, withArguments: FetchableItem.ball, FamilyMember.parent).andReturn(false)\nsubject.stub(function: .shouldFetch, withArguments: FetchableItem.ball, FamilyMember.kid).andReturn(true)\nfakeDog.shouldFetch(.slippers, for: .kid) // return true\nfakeDog.shouldFetch(.ball, for: .parent) // returns false\nfakeDog.shouldFetch(.ball, for: .kid) // returns true\n```\nAs was the case for argument list spying, there is a lot of \"I hope this works\" going on under the hood to make this approach work. Also like before, you can improve the stability and accuracty of this by conforming any types you care to check to `AnyEquatable`.\n\n## One last thing\n\nA technique I use a lot when writing generically typed methods that return the generic type, is to include the type itself as an argument and then give it a default argument, like I do on both methods that return a stubbed value:\n\n```swift\nfunc stubbedValue\u003cT\u003e(forFunction function: Function, asType: T.Type, arguments: [Any?]) -\u003e T\n// and\nfunc recordAndStub\u003cT\u003e(function: Function, asType: T.Type = T.self, arguments: Any?...) -\u003e T\n```\n\nWhat this does is give you options, as the caller. If you are assigning it to an explicitly typed variable or using it as the return line in a function, it can use type inference to determine what `T` is and that arument can disappear completely. (You'll notice that on `FakeDog` you never see the `asType:` argument.) But if you need to call the method and want to supply that type directly, you can easily pass it explicitly: \n```swift\nrecordAndstub(function: .shouldFetch, asType: Bool.self, arguments: item, familyMember)\n```\n\n## And that's it!\n\nThat's about all, folks. Anything more that you could want to know will be in the (forthcoming) documentation comments.\n\nThank you for taking the time to read through this. Feel free to reach out and ask about it, and to report any bugs you find. In the meantime, if you want a more stable, strongly-typed, fully-featured stubbing/spying framework that integrates seamlessly with Quick \u0026 Nimble, be sure to try the [Spry framework](https://github.com/Rivukis/Spry) on your next Swift project.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjakehawken%2Fposeur","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjakehawken%2Fposeur","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjakehawken%2Fposeur/lists"}