{"id":22112356,"url":"https://github.com/lukeredpath/lrmocky","last_synced_at":"2025-07-25T07:32:52.742Z","repository":{"id":48632738,"uuid":"798622","full_name":"lukeredpath/LRMocky","owner":"lukeredpath","description":"A port of jMock 2.0 for Objective-C","archived":false,"fork":false,"pushed_at":"2013-07-24T13:35:15.000Z","size":886,"stargazers_count":38,"open_issues_count":4,"forks_count":3,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-11-13T18:54:54.269Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"http://lukeredpath.co.uk","language":"Perl","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/lukeredpath.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}},"created_at":"2010-07-26T16:30:43.000Z","updated_at":"2023-12-25T22:36:23.000Z","dependencies_parsed_at":"2022-08-27T09:51:12.505Z","dependency_job_id":null,"html_url":"https://github.com/lukeredpath/LRMocky","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lukeredpath%2FLRMocky","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lukeredpath%2FLRMocky/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lukeredpath%2FLRMocky/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lukeredpath%2FLRMocky/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lukeredpath","download_url":"https://codeload.github.com/lukeredpath/LRMocky/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":227544088,"owners_count":17785198,"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":[],"created_at":"2024-12-01T10:57:41.889Z","updated_at":"2024-12-01T10:57:42.640Z","avatar_url":"https://github.com/lukeredpath.png","language":"Perl","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Mocky, an Objective-C mock objects framework\n\nMocky is an opinionated, [jMock](http:://jmock.org) inspired mock objects framework for OSX and iOS development. \n\nIt supports class and protocol-based mocking; a simple, expressive API designed for readability; Hamcrest matcher support using [OCHamcrest](http://github.com/hamcrest/OCHamcrest) and ships with support for OCUnit, or any testing framework built on top of it.\n\nNote: This document is currently a work in progress.\n\n## Getting Started\n\nThe recommended way of adding Mocky to your project is using [CocoaPods](http://cocoapods.org). Mocky is available as a static library for iOS and a framework for OSX. You can also clone the project repository and add it to Xcode to your workspace or project and build it as a dependency.\n\n### Installation\n\nIf you are using CocoaPods, all you need to do is add the following line to your Podfile:\n\n```ruby\npod 'Mocky'\n```\n\nUsing the framework on OSX is as simple as dragging it into your project. If you are using the static library in an iOS project, you'll also need to put the headers somewhere in your source tree, or reference them on disk by setting the header search path for your test bundle.\n\n## Quick Introduction\n\n[Borrowing from jMock once again](http://jmock.org/getting-started.html), we'll write a test for a simple publish/subscriber system. A publisher can publish messages (represented by strings) to one or more subscribers.\n\nFor all of the following examples, the test case `@interface` has been omitted.\n\n### A failing test\n\nBefore we can create mocks or set up any expectations, we need a context in which our mocks can exist. In Mocky, this context is defined by an instance of the 'LRMockery' class:\n\n```objc\n@implementation PublisherTest {\n  LRMockery *context;\n}\n\n- (void)setUp\n{\n  context = [LRMockery mockeryForTestCase:self];\n}\n\n@end\n```\n\nNow we'll write our first test. We want to assert that given a single subscriber, when we publish a message, the subscriber receives it. At this point, we've defined no classes or protocols other than our test case - we'll write the test first, then write the minimum we need to get the test to compile, before making it pass.\n\n```objc\n- (void)testSubscriberReceivesMessage\n{\n  id\u003cSubscriber\u003e subscriber = [context mock:@protocol(Subscriber)];\n    \n  Publisher *publisher = [[Publisher alloc] init];\n  [publisher addSubscriber:subscriber];\n  \n  NSString *message = @\"some message\";\n    \n  [context check:^{\n    [[expectThat(subscriber) receives] receive:message];\n  }];\n  \n  [publisher publish:message];\n  \n  assertContextSatisfied(context);\n}\n\n```\n\nThe last line of our test will check that all expectations have been met and will cause a test failure if they have not.\n\nSo far, the only design decisions we've made so far is that a subscriber will be any object that implements the `Subscriber` protocol. For this test, we don't care what a particular implementation of `Subscriber` does with the message it receives. We've also begun to define our `Publisher` interface. \n\n### Getting it to compile\n\nTo get this code to compile, we'll need to define the Subscriber protocol and Publisher class. We can do this in our test file for now.\n\n```objc\n@protocol Subscriber\n\n- (void)receives:(NSString *)message;\n\n@end\n\n@interface Publisher : NSObject\n\n- (void)addSubscriber:(id\u003cSubscriber\u003e)subscriber;\n- (void)publish:(NSString *)message;\n\n@end\n\n@implementation Publisher\n\n- (void)addSubscriber:(id\u003cSubscriber\u003e)subscriber\n{}\n  \n- (void)publish:(NSString *)message\n{}\n\n@end\n```\n\nNow we are able to compile our test, when we run it, it fails with the following error:\n\n```\nExpected \u003cmock Subscriber\u003e \n  to receive receive: with arguments: [\"some message\"] \n  exactly once but received it 0 times.\n```\n\n### Making the test pass\n\nWe can now write the simplest implementation possible to make this test work.\n\n```objc\n@implementation Publisher {\n  id\u003cSubscriber\u003e _subscriber;\n}\n\n- (void)addSubscriber:(id\u003cSubscriber\u003e)subscriber\n{\n  _subscriber = subscriber;\n}\n\n- (void)publish:(NSString *)message\n{\n  [_subscriber receive:message];\n}\n\n@end\n```\n\nIt's a quick first pass - we know we'll probably want to support multiple subscribers, but this should be enough to get the tests passing. We run them and they pass. \n\n### A second test\n\nLet's see if we can write a test for multiple subscribers.\n\n```objc\n- (void)testMultipleSubscribersReceiveMessages\n{\n  id\u003cSubscriber\u003e subscriberOne = [context mock:@protocol(Subscriber)];\n  id\u003cSubscriber\u003e subscriberTwo = [context mock:@protocol(Subscriber)];\n    \n  Publisher *publisher = [[Publisher alloc] init];\n  [publisher addSubscriber:subscriberOne];\n  [publisher addSubscriber:subscriberTwo];\n  \n  NSString *message = @\"some message\";\n    \n  [context check:^{\n    [[expectThat(subscriberOne) receives] receive:message];\n    [[expectThat(subscriberTwo) receives] receive:message];\n  }];\n  \n  [publisher publish:message];\n  \n  assertContextSatisfied(context);\n}\n\n```\n\nWe expect this test to fail, and when we run it, we get the following error:\n\n```\nExpected \u003cmock Subscriber\u003e \n  to receive receive: with arguments: [\"some message\"] \n  exactly once but received it 0 times.\n```\n\n### Disambiguating the error message\n\nThis is what we expected to happen but there is one small issue: the error doesn't really help us identify which subscriber didn't receive the message. Let's give our mocks a custom name and try running it again:\n\n```objc\n- (void)testMultipleSubscribersReceiveMessages\n{\n  id\u003cSubscriber\u003e subscriberOne = [context mock:@protocol(Subscriber) \n                                         named:@\"subscriber one\"];\n                                         \n  id\u003cSubscriber\u003e subscriberTwo = [context mock:@protocol(Subscriber) \n                                         named:@\"subscriber two\"];\n    \n  ...\n}\n\n```\n\nNow when we run the test, our error is a bit more useful:\n\n```\nExpected subscriber one \n  to receive receive: with arguments: [\"some message\"] \n  exactly once but received it 0 times.\n```\n\nNow we can update our implementation to get the test passing:\n\n```objc\n@implementation Publisher {\n  NSMutableSet *_subscribers;\n}\n\n- (id)init\n{\n  self = [super init];\n  if (self) {\n    _subscribers = [[NSMutableSet alloc] init];\n  }\n  return self;\n}\n\n- (void)addSubscriber:(id\u003cSubscriber\u003e)subscriber\n{\n  [_subscribers addObject:subscriber];\n}\n\n- (void)publish:(NSString *)message\n{\n  [_subscribers makeObjectsPerformSelector:@selector(receive:) withObject:message];\n}\n\n@end\n```\n\n## Defining Expectations\n\nAll expectations in LRMocky are defined within an expectation block. An expectation block is defined by calling the `-[LRMockery check:]` method. \n\nThe `check:` method can be called multiple times - the expectations from one block are appended to the expectations defined in any previous blocks.\n\nAll of the following code samples omit the `check:` block boilerplate and are assumed to be taking place within the context of the omitted block definition.\n\n### Expectation syntax\n\nThe general form for expectations can be defined as follows:\n\n```objc\n[[expectThat(\u003cmock object\u003e) \u003ccardinality-rule\u003e] \u003cselector-with-args\u003e]; \n  [then \u003caction\u003e];\n```\n\nThe `[then \u003caction\u003e]` part is optional and is used to define things that should happen when an expectation is met.\n  \n### Cardinality rules\n\nAll expectations have a cardinality, i.e. a number of calls that should happen for the expectation to be considered satisfied. The default cardinality for expectations is for them to be received exactly once. So the following:\n\n```objc\n[[expectThat(\u003cmock object\u003e) receives] someMethod];\n```\n\nIs the equivalent to:\n\n```objc\n[[expectThat(\u003cmock object\u003e) receivesExactly:1] someMethod];\n```\n\nYou can expect a minimum number of invocations:\n\n```objc\n[[expectThat(\u003cmock object\u003e) receivesAtLeast:1] of] someMethod];\n```\n\n_(the above example uses the optional `of` method - this is purely syntatic sugar and may be used to suit your taste)_.\n\nOr a maximum number of invocations:\n\n```objc\n[[expectThat(\u003cmock object\u003e) receivesAtMost:1] of] someMethod];\n```\n\nOr both:\n\n```objc\n[[expectThat(\u003cmock object\u003e) receivesBetween:1 and:3] of] someMethod];\n```\n\nIf you don't care how many times a particular method is called - including no calls at all - you could write:\n\n```objc\n[[expectThat(\u003cmock object\u003e) receivesAtLeast:0] of] someMethod];\n```\n\nA better way of expressing this is to use the `allowing()` function in place of `expectThat()`.\n\n```objc\n[allowing(\u003cmock object\u003e) someMethod];\n```\n\nIf you don't care what messages are sent to a mock object at all, you can ignore it entirely:\n\n```objc\nignoring(\u003cmock object\u003e);\n```\n\n### Actions\n\nActions take place when an expectation is matched and successfully invoked. You could configure a method call to return a canned value (e.g. stubbing):\n\n```objc\n[allowing(\u003cmock object\u003e) returnsSomeString]; \n  [then returns:@\"some string\"];\n```\n\nIf you need to return a primitive value, you can use `returnValue:` instead of `returns:`:\n\n```objc\n[allowing(\u003cmock object\u003e) returnsSomeInteger]; \n  [then returnsValue:(void *)123];\n```\n\nYou'll notice the need to cast the value to a void pointer to prevent a compiler warning. Alternatively, you can pass a boxed value to `returns:` and it will still work as expected:\n\n```objc\n[allowing(\u003cmock object\u003e) returnsSomeInteger]; \n  [then returns:@123];\n```\n\nAll of the above examples have been defined using `allowing()` instead of `expectThat()` - this is because you should prefer to stub queries, but only expect commands.\n\nYou can do more than return a value from an expected invocation. You can raise an exception:\n\n```objc\n[allowing(\u003cmock object\u003e) mightRaiseSomething]; \n  [then raisesException:\u003csome exception\u003e];\n```\n  \nYou can also perform an arbitrary block of code:\n\n```objc\n[allowing(\u003cmock object\u003e) returnsSomeString]; \n  [then performsBlock:^(NSInvocation *invocation) {\n    // do something here\n  }];\n```\n\nThe matched invocation will always be passed into the block, giving you a chance to modify it if you wish (you could conditionally set the return value here, for instance).\n\nYou can also perform multiple actions:\n\n```objc\n[allowing(\u003cmock object\u003e) returnsSomeString]; \n  [then doesAllOf:^(id\u003cLRExpectationActionSyntax\u003e actions) {\n    [actions returns:@\"return value\"];\n    [actions performsBlock:^(NSInvocation *unused) {\n      // do something else\n    }];\n  }];\n```\n\nIf you are expecting multiple calls to a method, you could configure the expectation to perform different actions on each call. For instance, if you wanted to specify that the third call to a method raises an exception, you could write:\n\n```objc\n[allowing(\u003cmock object\u003e) returnsSomeString]; \n  [then onConsecutiveCalls:^(id\u003cLRExpectationActionSyntax\u003e actions) {\n    [actions returns:@\"return value 1\"];\n    [actions returns:@\"return value 2\"];\n    [actions raisesException:\u003csome exception\u003e];\n  }];\n```\n\n### States\n\nSome times you want expectations to match only when in a particular state. This can be useful when testing that methods are only called in certain situations (normally after some other method call has taken place).\n\nYou can ask the mockery for a new state:\n\n```objc\nid readiness = [[context states:@\"readiness\"] startsAs:@\"unready\"];\n```\n\nWe can use this state object within our expectation blocks to constraint expectations to only occur in the given state:\n\n```objc\nwhenState([readiness equals:@\"ready\"], ^{\n  [[expectThat(testObject) receives] someMethod];\n});\n```\n\nIf we were to call `someMethod` on `testObject` in the current state, an unexpected invocation error would be raised.\n\nYou can trigger a state transition by calling `transitionTo:` on the state object, but more typically you would trigger this as the result of another expectation, using a state transition action:\n\n```objc\n[[expectThat(testObject) receives] doSomethingElse]; \n  [then state:readiness becomes:@\"ready\"];\n```\n\nNow, as long as our object under test calls `doSomethingElse` before calling `doSomething`, the test will pass.\n\n### Asynchronous behaviour\n\nSometimes, your expectations will be satisfied by invocations that are performed by some asynchronous operation. In order to test that these expectations are met, you need to be able to ask the context to wait for a given invocation.\n\nMocky supports asynchronous behaviour testing using states and a synchroniser, which can be invoked using the `waitUntil:` or `waitUntil:withTimeout:` methods on `LRMockery`.\n\nFirst of all, you need a state to represent the asynchronous nature of the call:\n\n```objc\nid operation = [[context states:@\"operation\"] startsAs:@\"waiting\"];\n```\n\nYou can now configure your expectation as normal, triggering a state change when it is invoked:\n\n```objc\n[[expectThat(testObject) receives] doSomethingElse]; \n  [then state:operation becomes:@\"finished\"];\n```\n\nFinally, you can ask the context to wait for the expected state; in this case, the finished state.\n\n```objc\n[context waitUntil:[operation equals:@\"finished\"]];\n```\n\nThe default timeout is 1 second, but this can be changed by passing in a different timeout value:\n\n```objc\n[context waitUntil:[operation equals:@\"finished\"] withTimeout:3];\n```\n\nYou will still need to place a call to `assertContextSatisfied()` after the `waitUntil:` call.\n\nIf the state change is never triggered, or the timeout is reached, the method will return and your test will fail as expected.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flukeredpath%2Flrmocky","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flukeredpath%2Flrmocky","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flukeredpath%2Flrmocky/lists"}