Readme.md in surrogate-0.5.5 vs Readme.md in surrogate-0.6.0
- old
+ new
@@ -15,43 +15,21 @@
for changes to a. Depending on the internals of the code (anything not shown in the readme) is
discouraged at this time. If you do want to do this (e.g. to make an interface for test/unit)
let me know, and I'll inform you / fork your gem and help update it, for any breaking changes
that I introduce.
-New Syntax
-==========
-
-Recently (v0.5.1), a new syntax was added:
-
-<table>
- <tr><th>Old</th><th>New</th></tr>
- <tr><td>.should have_been_told_to</td><td>.was told_to</td></tr>
- <tr><td>.should have_been_asked_for_its</td><td>.was asked_for</td></tr>
- <tr><td>.should have_been_asked_if</td><td>.was asked_if</td></tr>
- <tr><td>.should have_been_initialized_with</td><td>.was initialized_with</td></tr>
-</table>
-
-If you want to switch over, here is a shell script that should get you pretty far:
-
- find spec -type file |
- xargs ruby -p -i.old_syntax \
- -e 'gsub /should(_not)?(\s+)have_been_told_to/, "was\\1\\2told_to"' \
- -e 'gsub /should(_not)?(\s+)have_been_asked_(if|for)(_its)?/, "was\\1\\2asked_\\3"' \
- -e 'gsub /should(_not)(\s+)have_been_initialized_with/, "was\\1\\2initialized_with"' \
-
-
Features
========
* Declarative syntax
+* Can compare signatures of mocks to signatures of the class being mocked
* Support default values
* Easily override values
* RSpec matchers for asserting what happend (what was invoked, with what args, how many times)
* RSpec matchers for asserting the Mock's interface matches the real object
* Support for exceptions
* Queue return values
-* Initialization information is always recorded
Usage
=====
@@ -84,11 +62,14 @@
end
MockClient.new.request # => ["result1", "result2"]
```
-If you care about the **arguments**, your block can receive them.
+If you expect **arguments**, your block should receive them.
+This prevents the issues with dynamic mocks where arguments and parameters can diverge.
+It may seem like more work when you have to write the arguments explicitly, but you only
+need to do this once, and then you can be sure they match on each invocation.
```ruby
class MockClient
Surrogate.endow self
define(:request) { |limit| limit.times.map { |i| "result#{i.next}" } }
@@ -96,10 +77,11 @@
MockClient.new.request 3 # => ["result1", "result2", "result3"]
```
You don't need a **default if you set the ivar** of the same name (replace `?` with `_p` for predicates, since you can't have question marks in ivar names)
+Note that methods without bodies will not have their arguments checked, and will not be asserted against when comparing signatures.
```ruby
class MockClient
Surrogate.endow self
define(:initialize) { |id| @id, @connected_p = id, true }
@@ -175,11 +157,26 @@
user = MockUser.find 12
user.id # => 12
```
+Use **clone** to avoid altering state on the class.
+```ruby
+class MockUser
+ Surrogate.endow self do
+ define(:find) { new }
+ end
+end
+user_class = MockUser.clone
+user_class.find
+user_class.was told_to :find
+MockUser.was_not told_to :find
+```
+
+
+
RSpec Integration
=================
Currently only integrated with RSpec, since that's what I use. It has some builtin matchers
for querying what happened.
@@ -188,60 +185,75 @@
```ruby
require 'surrogate/rspec'
```
+Last Instance
+-------------
+
+Access the last instance of a class
+
+```ruby
+class MockMp3
+ Surrogate.endow self
+end
+
+mp3_class = MockMp3.clone # because you don't want to mutate the singleton
+mp3 = mp3_class.new
+mp3_class.last_instance.equal? mp3 # => true
+```
+
Nouns
-----
Given this mock and assuming the following examples happen within a spec
```ruby
class MockMP3
Surrogate.endow self
- define(:info) { 'some info' }
+ define(:info) { |song='Birds Will Sing Forever'| 'some info' }
end
```
-Check if **was invoked** with `have_been_asked_for_its`
+Check if **was invoked** with `was asked_for` (`was` is an alias of `should`, and `asked_for` is a custom matcher)
```ruby
-mp3.should_not have_been_asked_for_its :info
+mp3.was_not asked_for :info
mp3.info
-mp3.should have_been_asked_for_its :info
+mp3.was asked_for :info
```
Invocation **cardinality** by chaining `times(n)`
```ruby
mp3.info
mp3.info
-mp3.should have_been_asked_for_its(:info).times(2)
+mp3.was asked_for(:info).times(2)
```
Invocation **arguments** by chaining `with(args)`
```ruby
mp3.info :title
-mp3.should have_been_asked_for_its(:info).with(:title)
+mp3.was asked_for(:info).with(:title)
```
Supports RSpec's matchers (`no_args`, `hash_including`, etc)
```ruby
mp3.info
-mp3.should have_been_asked_for_its(:info).with(no_args)
+mp3.was asked_for(:info).with(no_args)
```
Cardinality of a specific set of args `with(args)` and `times(n)`
```ruby
mp3.info :title
mp3.info :title
mp3.info :artist
-mp3.should have_been_asked_for_its(:info).with(:title).times(2)
-mp3.should have_been_asked_for_its(:info).with(:artist).times(1)
+mp3.was asked_for(:info).with(:title).times(2)
+mp3.was asked_for(:info).with(:artist).times(1)
```
Verbs
-----
@@ -253,42 +265,42 @@
Surrogate.endow self
define(:play) { true }
end
```
-Check if **was invoked** with `have_been_told_to`
+Check if **was invoked** with `was told_to`
```ruby
-mp3.should_not have_been_told_to :play
+mp3.was_not told_to :play
mp3.play
-mp3.should have_been_told_to :play
+mp3.was told_to :play
```
Also supports the same `with(args)` and `times(n)` that nouns have.
Initialization
--------------
-Query with `have_been_initialized_with`, which is exactly the same as saying `have_been_told_to(:initialize).with(...)`
+Query with `was initialized_with`, which is exactly the same as saying `was told_to(:initialize).with(...)`
```ruby
class MockUser
Surrogate.endow self
define(:initialize) { |id| @id = id }
define :id
end
user = MockUser.new 12
user.id.should == 12
-user.should have_been_initialized_with 12
+user.was initialized_with 12
```
Predicates
----------
-Query qith `have_been_asked_if`, all the same chainable methods from above apply.
+Query qith `was asked_if`, all the same chainable methods from above apply.
```ruby
class MockUser
Surrogate.endow self
define(:admin?) { false }
@@ -296,28 +308,28 @@
user = MockUser.new
user.should_not be_admin
user.will_have_admin? true
user.should be_admin
-user.should have_been_asked_if(:admin?).times(2)
+user.was asked_if(:admin?).times(2)
```
-class MockUser
-
Substitutability
----------------
+Facets of substitutability: method existence, argument types, (and soon argument names)
+
After you've implemented the real version of your mock (assuming a [top-down](http://vimeo.com/31267109) style of development),
how do you prevent your real object from getting out of synch with your mock?
Assert that your mock has the **same interface** as your real class.
This will fail if the mock inherits methods which are not on the real class. It will also fail
if the real class has any methods which have not been defined on the mock or inherited by the mock.
-Presently, it will ignore methods defined directly in the mock (as it adds quite a few of its own methods,
-and generally considers them to be helpers). In a future version, you will be able to tell it to treat other methods
+Presently, it will ignore methods defined directly in the mock (as it generally considers them to be helpers).
+In a future version, you will be able to tell it to treat other methods
as part of the API (will fail if they don't match, and maybe record their values).
```ruby
class User
def initialize(id)end
@@ -346,10 +358,17 @@
# real class has extra methods
class UserWithNameAndAddress < UserWithName
def address()end
end
MockUser.should_not substitute_for UserWithNameAndAddress
+
+# signatures don't match
+class UserWithWrongSignature
+ def initialize()end # no id
+ def id()end
+end
+MockUser.should_not substitute_for UserWithWrongSignature
```
Sometimes you don't want to have to implement the entire interface.
In these cases, you can assert that the methods on the mock are a **subset**
of the methods on the real class.
@@ -403,11 +422,11 @@
to_return = user_id
user_id = user[:id]
to_return
end
- service.should have_been_told_to(:create).with { |block|
+ service.was told_to(:create).with { |block|
block.call_with({id: new_id}) # this will be given to the block
block.returns old_id # provide a return value, or a block that receives the return value (where you can make assertions)
block.before { user_id.should == old_id } # assertions about state of the world before the block is called
block.after { user_id.should == new_id } # assertions about the state of the world after the block is called
}
@@ -440,36 +459,5 @@
* [Kyle Hargraves](https://github.com/pd) for changing the name of his internal gem so that I could take Surrogate
* [David Chelimsky](http://blog.davidchelimsky.net/) for pairing with me to make Surrogate integrate better with RSpec
* [Corey Haines](http://coreyhaines.com/) for pairing on substitutability with me
* [Enova](http://www.enovafinancial.com/) for giving me time and motivation to work on this during Enova Labs.
* [8th Light](http://8thlight.com/) for giving me time to work on this during our weekly Wazas, and the general encouragement and interest
-
-
-TODO
-----
-
-* Remove dependency on all of RSpec and only depend on rspec-core, then have AC tests for the other shit
-* Add proper failure messages for block invocations
-* Add a better explanation for motivations
-* Figure out whether I'm supposed to be using clone or dup for the object -.^ (looks like there may also be an `initialize_copy` method I can take advantage of instead of crazy stupid shit I'm doing now)
-* don't blow up when delegating to the Object#initialize with args (do I still want this, or do I want to force arity matching (and maybe even variable name matching)?)
-* config: rspec_mocks loaded, whether unprepared blocks should raise or just return nil
-* extract surrogate/rspec into its own gem
-* support subset-substitutabilty not being able to touch real methods (e.g. #respond_to?)
-* Add a last_instance option so you don't have to track it explicitly
-* make substitutability matcher go either way
-* make substitutability matcher not care whether either are surrogates
-* Add support for operators
-
-
-Future Features
----------------
-
-* figure out how to talk about callbacks like #on_success
-* have some sort of reinitialization that can hook into setup/teardown steps of test suite
-* Support arity checking as part of substitutability
-* Ability to disassociate the method name from the test (e.g. you shouldn't need to change a test just because you change a name)
-* ability to declare normal methods as being part of the API
-* ability to declare a define that uses the overridden method as the body, but can still act like an api method
-* assertions for order of invocations & methods
-* class generator? (supports a top-down style of development for when you write your mocks before you write your implementations)
-* deal with hard dependency on rspec-mocks