ActiveMocker
Creates mocks from Active Record models. Allows your test suite to run very fast by not loading Rails or hooking to a database. It parse the schema definition and the defined methods on a model then saves a ruby file that can be included with a test. Mocks are regenerated when the schema is modified so your mocks will not go stale. This prevents the case where your units tests pass but production code is failing.
Example from a real app
Finished in 0.54599 seconds
190 examples, 0 failures
Installation
Add this line to your application's Gemfile:
gem 'active_mocker'
And then execute:
$ bundle
Or install it yourself as:
$ gem install active_mocker
Setup
config/initializers/active_mocker.rb
ActiveMocker::Generate.configure do |config|
# Required Options
config.schema_file = File.join(Rails.root, 'db/schema.rb')
config.model_dir = File.join(Rails.root, 'app/models')
config.mock_dir = File.join(Rails.root, 'spec/mocks')
# Logging
config.logger = Rails.logger
end
Here is an example of a rake task to regenerate mocks after every schema modifiation. If the model changes this rake task needs to be called manually. You could add a file watcher for when your models change and have it run the rake task.
lib/tasks/active_mocker.rake
task rebuild_mocks: :environment do
puts 'rebuilding mocks'
ActiveMocker.create_mocks
end
['db:schema:load', 'db:migrate', 'db:reset'].each do |task|
Rake::Task[task].enhance do
Rake::Task['rebuild_mocks'].invoke
end
end
Usage
db/schema.rb
ActiveRecord::Schema.define(version: 20140327205359) do
create_table "people", force: true do |t|
t.integer "account_id"
t.string "first_name", limit: 128
t.string "last_name", limit: 128
t.string "address", limit: 200
t.string "city", limit: 100
end
end
#app/models/person.rb
class Person < ActiveRecord::Base
belongs_to :account
def (name, type=nil)
puts name
end
def self.
end
end
spec/models/person_spec.rb
load 'spec/mocks/person_mock.rb'
PersonMock.column_names
=> ["id", "account_id", "first_name", "last_name", "address", "city"]
person_mock = PersonMock.new(first_name: "Dustin", last_name: "Zeisler", account: ActiveMocker.mock('Account').new)
=> "#<PersonMock id: nil, account_id: nil, first_name: \"Dustin\", last_name: \"Zeisler\", address: nil, city: nil>"
person_mock.first_name
=> "Dustin"
When schema.rb changes, the mock fails
db/schema.rb
ActiveRecord::Schema.define(version: 20140327205359) do
create_table "people", force: true do |t|
t.integer "account_id"
t.string "f_name", limit: 128
t.string "l_name", limit: 128
t.string "address", limit: 200
t.string "city", limit: 100
end
end
PersonMock.new(first_name: "Dustin", last_name: "Zeisler")
=>#<RuntimeError Rejected params: {"first_name"=>"Dustin", "last_name"=>"Zeisler"} for PersonMock>
Mocking instance and class methods
person_mock.('baz')
=> RuntimeError: :: is not Implemented for Class: PersonMock
person_mock.mock_instance_method(:bar) do |name, type=nil|
"Now implemented with #{name} and #{type}"
end
person_mock.new.('foo', 'type')
=> "Now implemented with foo and type"
person_mock.mock_class_method(:bar) do
"Now implemented"
end
When the model changes, the mock fails
app/models/person.rb
class Person < ActiveRecord::Base
belongs_to :account
def (name)
puts name
end
end
person_mock.new.('foo', 'type')
=> ArgumentError: wrong number of arguments (2 for 1)
app/models/person.rb
class Person < ActiveRecord::Base
belongs_to :account
def foo(name, type=nil)
puts name
end
end
person_mock.mock_instance_method(:bar) do |name, type=nil|
"Now implemented with #{name} and #{type}"
end
=> NoMethodError: undefined method `bar' for class `PersonMock'
ActiveRecord supported methods
class methods
- new
- create/create!
- column_names
- find
- find_by/find_by!
- find_or_create_by
- find_or_initialize_by
- where(conditions_hash)
- delete_all/destroy_all
- delete_all(conditions_hash)
- destroy(id)/delete(id)
- all
- count
- first/last
instance methods
- attributes
- update
- save/save!
- write_attribute/read_attribute - (private, can be used within an included module)
has_many associations
- empty?
- length/size/count
- uniq
- replace
- first/last
- concat
- include
- push
- clear
- take
Schema/Migration Option Support
- All schema types are supported and on initalization coerced by Virtus. If coercsion fails the passed value will be retained.
- Default value
Known Limitations
- Model names and table names must follow the default ActiveRecord naming pattern.
- Included/extended module methods will not be included on the mock. I suggest you keep domain logic out of the model and only add database queries. Domain logic can be put into modules and then included into the mock during test setup.
- Queries will not call other mocks classes, for example when using
whereall attributes must reside inside of each record.
Inspiration
Thanks to Jeff Olfert for being my original inspiration for this project.
Contributing
- Fork it ( http://github.com/zeisler/active_mocker/fork )
- Create your feature branch (
git checkout -b my-new-feature) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin my-new-feature) - Create new Pull Request
