h1. Aidmock
Aidmock is an safe mock and interfacing library for Ruby.
Aidmock doesn't target any specific mock or test framework. Our goal is to provide a wide solution, that supports the major mock and test frameworks. For now we are only supporting RSpec 2 and RSpec Mocks, but more drivers for other frameworks will be available soon.
The basic idea of Aidmock is we believe that Mocks are good, but they can be scary too, leading to false positives. Aidmock tries to make it safer. To read more about motivation, see the "motivation":https://github.com/wilkerlucio/aidmock/wiki/Motivation page.
Aidmock also generate some sanity checks for your interfaces, so defining interfaces can be a nice point when starting the defition of your classes. Let's start with setup and configuration, then we will see how to define our interfaces.
h2. Installation
To install Aidmock just run:
bc. gem install aidmock
Or add it to your Gemfile:
bc. gem "aidmock"
h2. Configuration
In order to use Aidmock, you need to configure it on your test environment. Since it's only working on RSpec for now you need to configure your @spec_helper@:
bc.. RSpec.configure do |config|
config.before :all do
Aidmock.setup! # it will do any nescessary setup, like extending your framework mocks
end
config.after :each do
Aidmock.verify! # it will verify created mocks after each spec
end
end
load or write your interfaces here
Aidmock::Sanity.sanitize! # it will run sanity checks, just call it after have all interfaces defined
h2. Automatic Interfacing
Since version 0.3.1 Aidmock supports automatic interfacing, this feature is enabled by default. With automatic interfacing Aidmock will define the class interface automatically for you if the interface is not already defined, we still encorage you to do some manual interface definitions since automatic interfacing can't be too much precise (it will generate method names and correct arity, but can't check value real types). But automatic interfaces is a nice start point for you at Aidmock, no need for big setups, work with third-part code (also built-in Ruby code too) and will give to you a new layer of safe to your mocks.
Even when you start doing interfaces by hand, you may want to still having this feature enabled, since it will interface third-part code and built-in code.
If you wanna do a hardcore Aidmock use, you can disable automatic interfacing with following snippet:
bc. Aidmock.autointerface = false
h2. How It Works
Aidmock runs after each test you do. It will get the mocks/stubs defined by your test and match these doubles with the interface you have defined for that object. If your mock doesn't respect your interface, it will raise an error, preventing you from having a false positive. Simple :)
h3. Remember to constrain your mocks
Sometimes you wanna do something like this:
bc. obj = mock
obj.should_receive(:something)
With Aidmock we can't detect the class with only this, so, for a safe mocking on this cases you should constrain the mock for one class or module that you already interfaced:
bc. obj = mock.constrained_to(SomeInterface)
obj.should_receive(:something)
This way can clear parse it.
h3. No interface warn
By default, Aidmock will warn you when you try to mock/stub something that you haven't interfaced. You can remove this warn with following snippet:
bc. Aidmock.warn_undefined_interface = false
h2. Interfacing
The major point of Aidmock is interfacing: it's when you define how your object's are supposed to work. It's really like you define methods on static languages as C or Java, but it's more dynamic and cool to work with. :)
Let's try it and define a simple interface:
bc.. Aidmock.interface Person do
method :first_name, String
method :first_name=, nil, String
method :last_name, String
method :last_name=, nil, String
method :full_name, String
end
p. Ok, let's take some time to look at what we are doing. We use a DSL created by Aidmock to define the class interface. In this case we use @method@ to describe an instance method. The first argument is the method name, the second one is the return, and after return we can send any number of params we want: they are the arguments.
The return and arguments in fact are matchers; they match if the value used corresponds to the matcher. In the example above, we used two different kind of matchers: the @KindOfMatcher@ (when we used the String class, it created this matcher) and the @AnythingMatcher@ (when we used nil).
This interface will automatically create some specs that check if these methods are defined on @Person@ class and if they respect the method arity. And most important, when you stub or mock the Person, it will check the interface and if you are mocking with correct params and return values; this will make your double safe.
h3. Class Methods
For interfacing class methods just use @class_method@ instead of @method@. Everything else is same for both.
h3. Method Names
The method names can be specified as symbol or regexp. Use symbol for method exact names and regexp for dynamic names.
bc. Aidmock.Interface MyAR do
method :table, Symbol
method /find_by_.+/, String
end
h2. Matchers
In section above we saw a simple example of how to create an interface. Now we will go deeper and see all the available matchers and how to use them.
h3(#conversions). Matcher Conversion
In most of cases, instead of creating a matcher directly, you will use a "Matcher Conversion". It takes simple values and create matchers based on them. In the table above you can see all available conversions:
|. Object Type |. Matcher |_. Description |
| Class | KindOfMatcher | if object is class, it will create a @KindOfMatcher@ with the given class |
| Array | AnyMatcher | if the object is an array, it will create an @AnyMatcher@, where each item of array will be an matcher |
| nil | AnythingMatcher | if the object is nil, it will create an @AnythingMatcher@ |
| Symbol | DuckTypeMatcher | if the object is a symbol, it will create an @DuckTypeMatcher@ that responds to it |
| Hash | HashMatcher | if the object is an hash, it will create an @HashMatcher@ with given hash |
h3(#any_matcher). AnyMatcher
This matcher can be used when you want to have more than one matcher option. It takes a list of matchers (or use a value to be converted by "conversions":#conversions) and it will match if any of these matchers matches.
DSL helper: @any_of(*matchers)@
Conversion: use an array
Example:
bc. method :concat, String, [String, Fixnum]
method :concat, String, any_of(String, Fixnum) # same as line above
h3(#anything_matcher). AnythingMatcher
This matcher will simply accept anything.
DSL Helper: @anything@
Conversion: use a @nil@
Example:
bc. method :puts, nil
method :puts, anything #same as line above
h3(#duck_type_matcher). DuckTypeMatcher
Duck type matcher will check if an object responds to all the given methods.
DSL Helper: @respond_to(*methods)@
Conversion: use a symbol
Example:
bc. method :write, :to_s
method :write, respond_to(:to_s) # same as line above
h3(#instance_of_matcher). InstanceOfMatcher
Check if the object is the instance of given class.
DSL Helper: @instance_of(klass)@
Example:
bc. method :to_s, instance_of(String)
h3(#kind_of_matcher). KindOfMatcher
Check the object is the kind of given class.
DSL Helper: @kind_of(klass)@
Conversion: use any class
Example:
bc. method :find, ActiveRecord::Base
method :find, kind_of(ActiveRecord::Base) # same as above line
h3(#hash_matcher). HashMatcher
The hash matcher checks if the argument is a hash and each key defined on it. If the user sends a hash with a key that is not present on the hash definition, it will fail. It also checks the value type of each key. By default it will ignore if the user doesn't send all expected options (which is a common pattern for options arguments), but it has a strict mode that will require all the keys to be defined.
DSL Helper: @hash_including(hash, strict = false)@
Conversion: use any hash
Example:
bc. method :find, {:conditions => [String, Array], :order => String}
method :find, has_including(:conditions => [String, Array], :order => String) # same as above
method :find, has_including({:conditions => [String, Array], :order => String}, true) # will require to be called with all keys defined
h3(#not_nil_arg_matcher). NotNilArgMatcher
By default, all matchers will accept a @nil@ value. If you want to require the value (making a @nil@ return false), you can use this matcher. You also need to send a matcher as param (to check when value is not nil, you can use the conversions too).
DSL Helper: @not_nil(matcher)@ or @nn(matcher)@
Example:
bc. method :name=, nil, nn(String)
method :name=, nil, not_nil(String) # same as above
h3(#optional_arg_matcher). OptionalArgMatcher
When you want optional arguments, the @OptionalArgMatcher@ will solve it for you. This matcher is only valid for arguments.
DSL Helper: @optional(matcher)@ or @o(matcher)@
Example:
bc. method :something, nil, o(String)
method :something, nil, optional(String) # same as above
h3(#splat_arg_matcher). SplatArgMatcher
When you want to use splat arguments (example: @def thing(*args)@) this matcher will interface it, you also need to send an matcher (or conversion) to it, and each value of splat will be matched by this matcher. This matcher is only valid for arguments.
DSL Helper: @splat(matcher)@ or @s(matcher)@
Example:
bc. method :something, s(nil) # will accept splat with anything(nil will be converted one AnythingMatcher)
method :something, splat(nil) # same as above
h2. Mixing automatic and manual interfaces
Why just manual OR automatic interfacing? You can have both!
You can tell Aidmock to fill the interface with automatic generated methods, and them just override with manual interface, to do this, just send @:auto => true@ to interface definition:
bc.. Aidmock.interface Person, :auto => true do
method :name, String
end
h2. Class Inheritance and Modules
Aidmock respects your class inheritance and module definition, so, the below example will be valid:
bc.. class Animal
def scream(noise)
puts noise.to_s + "!!!"
end
end
class Dog < Animal
end
Aidmock.interface Animal do
method :scream, String, :to_s
end
it "test inheritance" do
dog = mock.constrained_to(Dog)
dog.stub(:scream).with("ha").and_return("ha!!!") # this stub will be verified as you expect
end
h2. Changelog
h3. 0.4.0
- Fixed automatic generation step
- Hability to mix automatic interfacing with manual interface
- Methods: Aidmock.setup, Aidmock.verify, Aidmock::Sanity.sanitize now requires ! (now you should use Aidmock.setup!, Aidmock.verify!, Aidmock::Sanity.sanitize!)
h3. 0.3.1
- Supporting automatic interfacing
h3. 0.3.0
- bad release, ignore this version please
h3. 0.2.0
- Added .constrained_to to mocks
h3. 0.1.0
h2. Feedback
Aidmock still be somekind of experimental project, any feedback will help a lot. Please use github issues for reporting any bug and/or suggestion :)