Skip to content
This repository

Safe mocking

branch: master

Fetching latest commit…

Octocat-spinner-32-eaf2f5

Cannot retrieve the latest commit at this time

Octocat-spinner-32 examples
Octocat-spinner-32 lib
Octocat-spinner-32 spec
Octocat-spinner-32 .gitignore
Octocat-spinner-32 Gemfile
Octocat-spinner-32 Gemfile.lock
Octocat-spinner-32 README.textile
Octocat-spinner-32 Rakefile
Octocat-spinner-32 VERSION
Octocat-spinner-32 aidmock.gemspec
README.textile

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 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.

Installation

To install Aidmock just run:

gem install aidmock

Or add it to your Gemfile:

gem "aidmock"

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:

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

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:

Aidmock.autointerface = false

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 :)

Remember to constrain your mocks

Sometimes you wanna do something like this:

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:

obj = mock.constrained_to(SomeInterface)
obj.should_receive(:something)

This way can clear parse it.

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:

Aidmock.warn_undefined_interface = false

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:

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

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.

Class Methods

For interfacing class methods just use class_method instead of method. Everything else is same for both.

Method Names

The method names can be specified as symbol or regexp. Use symbol for method exact names and regexp for dynamic names.

Aidmock.Interface MyAR do
  method :table, Symbol
  method /find_by_.+/, String
end

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.

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

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) and it will match if any of these matchers matches.

DSL helper: any_of(*matchers)
Conversion: use an array

Example:

method :concat, String, [String, Fixnum]
method :concat, String, any_of(String, Fixnum) # same as line above

AnythingMatcher

This matcher will simply accept anything.

DSL Helper: anything
Conversion: use a nil

Example:

method :puts, nil
method :puts, anything #same as line above

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:

method :write, :to_s
method :write, respond_to(:to_s) # same as line above

InstanceOfMatcher

Check if the object is the instance of given class.

DSL Helper: instance_of(klass)

Example:

method :to_s, instance_of(String)

KindOfMatcher

Check the object is the kind of given class.

DSL Helper: kind_of(klass)
Conversion: use any class

Example:

method :find, ActiveRecord::Base
method :find, kind_of(ActiveRecord::Base) # same as above line

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:

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

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:

method :name=, nil, nn(String)
method :name=, nil, not_nil(String) # same as above

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:

method :something, nil, o(String)
method :something, nil, optional(String) # same as above

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:

method :something, s(nil) # will accept splat with anything(nil will be converted one AnythingMatcher)
method :something, splat(nil) # same as above

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:

Aidmock.interface Person, :auto => true do
  method :name, String
end

Class Inheritance and Modules

Aidmock respects your class inheritance and module definition, so, the below example will be valid:

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

Changelog

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!)

0.3.1

  • Supporting automatic interfacing

0.3.0

  • bad release, ignore this version please

0.2.0

  • Added .constrained_to to mocks

0.1.0

  • initial version

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 :)

Something went wrong with that request. Please try again.