{"id":16732653,"url":"https://github.com/randycoulman/doubleagents","last_synced_at":"2025-03-15T19:44:26.646Z","repository":{"id":150607418,"uuid":"9674370","full_name":"randycoulman/DoubleAgents","owner":"randycoulman","description":"Test Double library for Cincom Visualworks Smalltalk.","archived":false,"fork":false,"pushed_at":"2018-03-02T15:12:33.000Z","size":170,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-01-22T08:47:29.794Z","etag":null,"topics":["mock","smalltalk","stub","test-doubles"],"latest_commit_sha":null,"homepage":null,"language":null,"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/randycoulman.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"License.txt","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":"2013-04-25T15:01:17.000Z","updated_at":"2018-07-25T16:46:54.000Z","dependencies_parsed_at":"2023-06-12T01:15:27.782Z","dependency_job_id":null,"html_url":"https://github.com/randycoulman/DoubleAgents","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/randycoulman%2FDoubleAgents","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/randycoulman%2FDoubleAgents/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/randycoulman%2FDoubleAgents/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/randycoulman%2FDoubleAgents/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/randycoulman","download_url":"https://codeload.github.com/randycoulman/DoubleAgents/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243784099,"owners_count":20347409,"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","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","smalltalk","stub","test-doubles"],"created_at":"2024-10-12T23:45:58.645Z","updated_at":"2025-03-15T19:44:26.624Z","avatar_url":"https://github.com/randycoulman.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# DoubleAgents\n\n![Maintenance Status](https://img.shields.io/badge/maintenance-active-green.svg)\n\nCreate and use \"test doubles\" in unit tests.\n\nDoubleAgents is licensed under the MIT license.  See the copyright tab\nin the RB, the 'notice' property of this package, or the License.txt\nfile on GitHub.\n\nDoubleAgents' primary home is the\n[Cincom Public Store Repository](http://www.cincomsmalltalk.com/CincomSmalltalkWiki/Public+Store+Repository).\nCheck there for the latest version.  It is also on\n[GitHub](https://github.com/randycoulman/DoubleAgents).\n\nDoubleAgents was developed in VW 7.10.1, but is compatible with VW 7.7\nand later.  If you find any incompatibilities with VW 7.7 or later,\nlet me know (see below for contact information) or file an issue on\nGitHub.\n\n# Introduction\n\nDoubleAgents is a library for creating and working with \"test doubles\"\n(think \"stunt doubles\" in acting) when writing unit tests.  It is\noptimized for keeping test code as readable as possible.\n\nDoubleAgents supports two kinds of test doubles. See\n[Mocks Aren't Stubs](http://martinfowler.com/articles/mocksArentStubs.html)\nfor more information.\n\n* Stubs are simple objects that don't have any real behavior.  Each\n  stubbed method does no work and returns a given value.\n\n* Mocks are objects that are pre-programmed with a set of\n  \"expectations\".  After running the code under test, the mock is\n  checked to see if all of the expectations have been met.\n\nIn this library, all test doubles are instances of `DoubleAgent` and\ncan contain a mixture of stubbed methods and expectations.\n`DoubleAgent`s can be created directly and used in place of instances\nof other classes.  In addition, it is possible to stub or mock methods\non existing instances (including class-side methods of existing\nclasses).  It is also possible to stub or mock methods on all\ninstances of a class.  In these latter cases, all non-doubled\nmethods will continue to function like normal.\n\nDoubleAgents was designed to support unit testing in the style\nrecommended by [Sandi Metz](https://twitter.com/sandimetz) in her\nexcellent\n[Practical Object-Oriented Design in Ruby: An Agile Primer](http://www.poodr.info/book/).\nSee her\n[slides from a talk at Ancient City Ruby](https://speakerdeck.com/skmetz/magic-tricks-of-testing)\nfor a good summary of her advice.\n\n# Creating a DoubleAgent\n\nThere are four kinds of `DoubleAgent`:\n\n* A standalone `DoubleAgent` acts as an instance of its target class.\nThis is the normal usage.  A standalone `DoubleAgent` is created by\nsending one of the `#expect:*` or `#stub:*` API methods directly to a\nclass, by sending `#doubleAgent` to the class, or by explicitly\ncreating the `DoubleAgent`.\n\n* `AClass expect: #aMessage [...]`\n* `AClass stub: #aMessage [...]`\n* `AClass doubleAgent`\n* `DoubleAgent of: AClass`\n\n* An in-place instance-side `DoubleAgent` is used for objects that are\npart of a more complex object structure where it is difficult to\ninject a standalone `DoubleAgent` as a dependency.  It allows\nselective mocking and stubbing, where only a few methods are doubled\nand all other methods have their normal behavior.  An in-place\ninstance-side `DoubleAgent` is created by sending one of the\n`#expect:*` or `#stub:*` API methods directly to the instance, by\nsending `#doubleAgent` to the instance, or by explicitly creating the\n`DoubleAgent`.\n\n* `anInstance expect: #aMessage [...]`\n* `anInstance stub: #aMessage [..]`\n* `anInstance doubleAgent`\n* `DoubleAgent around: anInstance`\n\n* An in-place class-side `DoubleAgent` is similar to an in-place\ninstance-side double, but is used for mocking or stubbing class-side\nmethods of a class.  Note that doubled methods on classes are global\nthroughout the system, so use this facility with care.  For example,\nattempting to stub something like `Timer class\u003e\u003eafter:do:` will hang the\ndebugger.  An in-place class-side `DoubleAgent` is created by sending\n`#classSideDouble` to the class, or by explicitly creating the\n`DoubleAgent`.\n\n* `aClass classSideDouble`\n* `DoubleAgent around: AClass`\n\n* An any-instance `DoubleAgent` is used when it is necessary to mock\nor stub methods on instances of a class that are not directly visible\nto the test.  Any-instance doubles should be used very rarely, as they\ngenerally suggest that there is a more fundamental problem with the\ndesign of the code under test.  However, there are times when they are\nneeded.  An any-instance `DoubleAgent` is created by sending\n`#anyInstanceDouble` to the class, or by explicitly creating the\n`DoubleAgent`.\n\n* `aClass anyInstanceDouble`\n* `DoubleAgent forAnyInstanceOf: AClass`\n\n# Stubbing and Mocking Methods\n\nThere is a family of methods for creating method stubs and defining\nmethod expectations.  These methods can be sent to a `DoubleAgent`, to\na class (which automatically creates and returns a `DoubleAgent`), or\nto an object (which stubs or mocks the method \"in place\").\n\nA method may only be mocked or stubbed if it is understood by the\nclass or instance being doubled.  This is to provide extra assurance\nthat the test double uses the same API as the real class.\n\n## Return Values\n\nAll stubbed and mocked methods return a value when they are called.\n\n* If the API call doesn't specify a return value, then `self` itself is returned.\n* If the API call ends with `return: anObject`, then `anObject` is returned from every call to the method.\n* If the API call ends with `do: aBlock`, then `aBlock` is evaluated\n  with the arguments passed to the method, and the result is returned.\n  Note that the arguments are \"culled\" to the block, so the block\n  doesn't need to specify the arguments if it doesn't need them.\n\n## Stubbing a Method\n\nTo stub a method, use one of the following:\n\n* `stub: aMessage`\n* `stub: aMessage return: anObject`\n* `stub: aMessage do: aBlock`\n\nThese methods do nothing but return a value as outlined above.\n\nIf the arguments are important, use one of the following:\n\n* `stub: aMessage with: anObject`\n* `stub: aMessage with: anObject with: anotherObject`\n* `stub: aMessage with: anObject with: anotherObject with: aThirdObject`\n* `stub: aMessage withArguments: aCollection`\n* `stub: aMessage with: anObject return: anObject`\n* `stub: aMessage with: anObject with: anotherObject return: anObject`\n* `stub: aMessage with: anObject with: anotherObject with: aThirdObject return: anObject`\n* `stub: aMessage withArguments: aCollection return: anObject`\n* `stub: aMessage with: anObject do: aBlock`\n* `stub: aMessage with: anObject with: anotherObject do: aBlock`\n* `stub: aMessage with: anObject with: anotherObject with: aThirdObject do: aBlock`\n* `stub: aMessage withArguments: aCollection do: aBlock`\n\nThe general forms are the `#stub:withArguments:*` methods; the others\nare provided as convenient shortcuts.  These stubs are used when\n`aMessage` is sent with arguments that are \"congruent\" (see below) to\nthose specified.  They do nothing else except return a value as outlined above.\n\nFor even more flexible argument checking, use one of the following:\n\n* `stub: aMessage where: aBlock`\n* `stub: aMessage where: aBlock return: anObject`\n* `stub: aMessage where: aBlock do: returnBlock`\n\nThese stubs are used when `aMessage` is sent with arguments that\nsatisfy `aBlock`.  `aBlock` must take some or all of the arguments and\nreturn a Boolean indicating whether the arguments are satisfactory.\nThey do nothing else except return a value as outlined above.\n\nThe argument-matching forms of the `#stub:*` methods are only\nuseful when you need to respond differently to a stubbed message\ndepending on one or more of the arguments.  This is needed\noccasionally, but only after careful consideration of other options.\nIn general, you should prefer non-argument-matching forms above.\n\n## Disallowing a Message\n\nSometimes, it is desirable to explicitly state that a particular\nmessage will not be sent to an object.  To disallow a message send,\nuse the following:\n\n* `disallow: aMessage`\n\nIf a disallowed message is sent, a `BurnNotice` is raised.\n\n## Defining Expectations for a Method\n\nIn order to verify that a message is sent to an object, use one of the\nfollowing:\n\n* `expect: aMessage`\n* `expect: aMessage return: anObject`\n* `expect: aMessage do: aBlock`\n\nThese methods check that `aMessage` was sent, but do not perform any\nchecks on the arguments.  They do nothing else except return a value\nas outlined above.\n\nIf the arguments are important, use one of the following:\n\n* `expect: aMessage with: anObject`\n* `expect: aMessage with: anObject with: anotherObject`\n* `expect: aMessage with: anObject with: anotherObject with: aThirdObject`\n* `expect: aMessage withArguments: aCollection`\n* `expect: aMessage with: anObject return: anObject`\n* `expect: aMessage with: anObject with: anotherObject return: anObject`\n* `expect: aMessage with: anObject with: anotherObject with: aThirdObject return: anObject`\n* `expect: aMessage withArguments: aCollection return: anObject`\n* `expect: aMessage with: anObject do: aBlock`\n* `expect: aMessage with: anObject with: anotherObject do: aBlock`\n* `expect: aMessage with: anObject with: anotherObject with: aThirdObject do: aBlock`\n* `expect: aMessage withArguments: aCollection do: aBlock`\n\nThe general forms are the `#expect:withArguments:*` methods; the\nothers are provided as convenient shortcuts.  These methods check that\n`aMessage` was sent with arguments that are \"congruent\" (see below) to\nthose specified.  They do nothing else except return a value as\noutlined above.\n\n## Argument Congruency\n\nArgument congruency is implemented using the `#===` method provided by\nthe [Threequals](https://github.com/randycoulman/Threequals) package.\nSee that package for more details, but as a summary:\n\n* Objects that are `#=` are also `#===`.\n* A class is `#===` to an object that `#isKindOf:` the class.\n* A block is `#===` to an object if it evaluates to true when passed\n  the object.\n* An interval is `#===` to a number that is between the endpoints of\n  the interval, including the endpoints.\n* If Threequals-Regex is loaded, a regular expression is `#===` to a\n  string that matches it.\n\nUsing `#===` allows for expectations like the following:\n\n```\nmyDouble expect: #with:and:do:\n         with: (40 to: 42)\n         with: [:x | x even]\n         with: BlockClosure\n```\n\nThis expectation will be satisfied if the arguments are some number\nbetween 40 and 42 inclusive, an even number, and a block.\n\nFor even more flexible argument checking, use one of the following:\n\n* `expect: aMessage where: aBlock`\n* `expect: aMessage where: aBlock return: anObject`\n* `expect: aMessage where: aBlock do: returnBlock`\n\nThese methods check that `aMessage` was sent with arguments that\nsatisfy `aBlock`.  `aBlock` must take some or all of the arguments and\nreturn a Boolean indicating whether the arguments are satisfactory.\nThey do nothing else except return a value as outlined above.\n\n## Verifying that Expectations Are Met\n\nIf an unexpected message is sent to a `DoubleAgent`, it will\nimmediately raise a `BurnNotice` exception.  It is necessary to check\nthat all expectations are met at the end of a test.  All DoubleAgents\nregister with a singleton instance of `Agency`.  `Agency` is\nresponsible for verifying all of the `DoubleAgent`s and for ensuring\nthat they clean up after themselves.  If an expectation is not met, a\n`BurnNotice` exception will be raised.\n\n`Agency` provides several options for verification and cleanup.\n\n* `Agency class\u003e\u003etearDown` verifies all registered `DoubleAgent`s and\n  then ensures that any cleanup actions they need to perform are done.\n  Only the first `BurnNotice` will be reported.  All cleanup actions\n  will be performed even if a `BurnNotice` is raised, and even if a\n  cleanup action raises an exception.  This method should be called\n  from the `tearDown` of your test class.  If your `tearDown` method\n  performs other actions that might fail, it is recommended that you\n  use an `#ensure:` block to guarantee that `Agency class\u003e\u003etearDown`\n  is sent in all cases.\n\n* `Agency class\u003e\u003esetUp` verifies that the `Agency` was torn down\n  correctly by the last test that used it.  If not, a `BurnNotice`\n  will be raised.  This may not help figure out which test failed to\n  tear down the `Agency`, but will alert you to a problem and ensure\n  that each test starts out in a clean state.  This method should be\n  called from the `setUp` of your test class.\n\n* If you use SUnitToo, `DoubleAgentTestCase` (in\n  DoubleAgents-SUnitToo) implements `setUp` and `tearDown` methods\n  that forward to the `Agency`.  You can have your test class inherit\n  from `DoubleAgentTestCase` to ensure that the `Agency` is managed\n  properly.  Make sure that your `setUp` and `tearDown` also send to\n  `super` in addition to their own actions.  If your `tearDown` method\n  performs other actions that might fail, it is recommended that you\n  use an `#ensure:` block to guarantee that the superclass `tearDown`\n  is sent in all cases.\n\n* `Agency class\u003e\u003everifyAfter: aBlock` wraps `aBlock` with `setUp` and\n  `tearDown` calls.  This method is handy for a single test that uses\n  `DoubleAgent`s.  For multiple tests in a class, though, it is better\n  to use one of the earlier options.\n\n* `aBlock verifyAgents` is a handy shortcut for `verifyAfter:`.  You\n  can wrap the body of your test in a block and send `verifyAgents` to\n  the block.\n\n* `Agency class\u003e\u003eforceReset` ensures that the `Agency` is cleaned up\n  correctly, but does not perform any verification.  This method\n  should not be used in normal circumstances, but can be used in a\n  pinch if your image gets left in a bad state somehow.\n\n## Flexible vs Strict\n\nBy default, standalone `DoubleAgent`s are \"strict\".  That is, they\nonly allow messages to be sent that have been stubbed or mocked.  All\nother message sends raise a `BurnNotice`.  For in-place\n`DoubleAgent`s, messages that have not been stubbed or mocked\nimplement their normal behavior.  A \"flexible\" `DoubleAgent` will\nallow other messages to be sent; they will simply return `self`.\n\n`DoubleAgent` implements `#flexible` and `#strict` to convert between\nthe two.\n\n## Ordered Sends\n\nBy default, mock expectations are \"unordered\".  That is, the messages\ncan be sent to the `DoubleAgent` in any order.  An \"ordered\"\n`DoubleAgent` requires the messages to be sent in the specified order.\nIt will raise a `BurnNotice` if any messages are sent out of order.\n\n`DoubleAgent` implements `#ordered` and `#unordered` to convert\nbetween the two.\n\n## Conflicts\n\nThe same method can be mocked or stubbed repeatedly.  The rule is\n\"last one wins\".  That is, if you first stub a method, and then later\nset a mock expectation on it, then the method will be a mock that is\nverified.  Similarly, if you set a mock expectation on a method and\nthen later stub the same method, then it will be a stub.  Setting a\nmock expectation on a method that is already a mock simply adds the\nnew expectation to the existing expectations; it means that the method\nmust be sent more than once.\n\nA common pattern is to stub a method in a test's `setUp`, and then in\none or more tests, set a mock expectation to verify that the message\nis sent to the object.\n\n# Understanding the Code\n\n`DoubleAgent` is the central class in this library.  It has subclasses\nthat implement the four main types of agent: `StandaloneDouble`,\n`InPlaceInstanceDouble`, `InPlaceClassDouble`, and\n`AnyInstanceDouble`.\n\nAll `DoubleAgent`s register with the singleton `Agency`, which is\nresponsible for verifying all mock expectations and cleaning up.\n\nWhen verifying expectations, method arguments are verified by an\n`ArgumentPolicy` such as `IgnoreArguments`, `ArgumentsEqual`, or\n`ArgumentsMatch`.\n\nDoubled methods are represented by a `MethodDouble`, either\n`MockMethod` or a `StubMethod`.\n\n# Acknowledgements\n\nI stood on the shoulders of several giants when implementing this\nlibrary.\n\nAs already mentioned, I was inspired to write this library by trying\nto follow [Sandi Metz](https://twitter.com/sandimetz)'s testing advice\nin\n[Practical Object-Oriented Design in Ruby: An Agile Primer](http://www.poodr.info/book/).\n\nI looked at several other test double libraries for API and\nimplementation ideas, including:\n\n* [Smallmock](http://www.cincomsmalltalk.com/publicRepository/SmallMock.html)\n* [Flexmock](https://github.com/jimweirich/flexmock)\n* [JMock](http://jmock.org/)\n* [RSpec mocks](https://github.com/rspec/rspec-mocks)\n\nThe in-place double implementations use some clever tricks that were\ninspired by the\n[MethodWrappers](http://www.refactory.com/tools/method-wrappers)\nproject and a couple of blog posts by\n[Travis Griggs](http://objology.blogspot.com/):\n\n* [Superpower Adventures in Lightweight Classing](http://www.cincomsmalltalk.com/userblogs/travis/blogView?showComments=true\u0026entry=3440856756)\n* [Mutating your Process, for Instance](http://www.cincomsmalltalk.com/userblogs/travis/blogView?showComments=true\u0026entry=3421076502)\n\n# Contributing\n\nI'm happy to receive bug fixes and improvements to this package.  If\nyou'd like to contribute, please publish your changes as a \"branch\"\n(non-integer) version in the Public Store Repository and contact me as\noutlined below to let me know.  I will merge your changes back into\nthe \"trunk\" as soon as I can review them.\n\n# Contact Information\n\nIf you have any questions about DoubleAgents and how to use it, feel free to contact me.\n\n* Web site: http://randycoulman.com\n* Blog: Courageous Software (http://randycoulman.com/blog)\n* E-mail: randy _at_ randycoulman _dot_ com\n* Twitter: @randycoulman\n* GitHub: randycoulman\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frandycoulman%2Fdoubleagents","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frandycoulman%2Fdoubleagents","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frandycoulman%2Fdoubleagents/lists"}