I'm trying to optimise my specs a bit. I usually have a problem creating test data for nested resources and users. I usually end up with a before(:each) that sets up the data, this is run by more than 120 of my specs. Let me show you: (it's not accurate, but you should get the point)
def setup_test_data
#user = FactoryGirl.create(:admin_with_account)
#account = #user.account
3.times do |n|
list = FactoryGirl.create(:list)
list.items << FactoryGirl.create_list(:item, 3)
#account.lists << list
end
end
before(:each){setup_test_data}
subject{List.merge(list1, list2)}
it{should have(6).items}
And here is why I fail to shorten my test data setup
def self.merge(lists)
merged_list = lists.first.account.subscriber_lists.build
name = "Merge of "
lists.each do |list|
name << "'#{list.name}', "
list.items.each do |item|
merged_list.items.build(item.dup.attributes)
end
end
merged_list.name = name.chop.chop
merged_list.save!
merged_list.reload # I use this to filter out duplicates via validations
end
My Options:
A) move some logic back into the controller, less dependency on the account, save in the controller
B) stub/mock a lot more, but with nested resources + associations it's hard to do
C) your idea here:
Thanks
Ray
C) Create your own RSpec rake task that will first import some basic data, then use DatabaseCleaner to make sure everything runs transactionally (you will have to manually clear the DB after your custom rake tasks, because for some reason it doesn't seem to be, but with DatabaseCleaner this is a one-liner).
I use this in a situation where I have a large pre-defined dataset that I need to test against and want it to be created once, then have tests performed transactionally against it.
If this appeals to you, let me know and I can provide more code to help you out.
Related
Let's say we have class:
class Post
def save
# implementation
end
def self.find(id)
#implementation
end
end
I struggle with testing #save and .find, I've:
describe '#save' do
it 'saves the post' do
subject.save
created = Post.find(subject.id)
expect(created).to eq(subject)
end
end
describe '.find' do
it 'finds the post' do
subject.save
created = Post.find(subject.id)
expect(created).to eq(subject)
end
end
In case of #save method I'd like to check side effect, in case of .find I'd like to test returned value. How to cope with this case without duplicating specs ?
In this case, to isolate the save and find actions, you need to mock the repository.
Whether you are writing to a DB, a file-system, cache, or whatever - you can mock it to either expect the saving feature, or set it up (before the beginning of the test) to make sure find works.
For most repository implementations there are gems to mock them (Factory Girl for relational databases, FakeFS for file-system), but you can roll your own if you have some exotic repository no one has heard of.
This way you test save without using find, or vice versa.
I'm following a TDD approach to building our app, and creating a whole bunch of service objects, keeping models strictly for data management.
Many of the services I've built interface with models. Take for example MakePrintsForRunner:
class MakePrintsForRunner
def initialize(runner)
#runner = runner
end
def from_run_report(run_report)
run_report.photos.each do |photo|
Print.create(photo: photo, subject: #runner)
end
end
end
I appreciate the create method could arguably be abstracted into the Print model, but let's keep it as is for now.
Now, in the spec for MakePrintsForRunner I'm keen to avoid including spec_helper, since I want my service specs to be super fast.
Instead, I stub out the Print class like this:
describe RunnerPhotos do
let(:runner) { double }
let(:photo_1) { double(id: 1) }
let(:photo_2) { double(id: 2) }
let(:run_report) { double(photos: [photo_1, photo_2]) }
before(:each) do
#service = RunnerPhotos.new(runner)
end
describe "#create_print_from_run_report(run_report)" do
before(:each) do
class Print; end
allow(Print).to receive(:create)
#service.create_print_from_run_report(run_report)
end
it "creates a print for every run report photo associating it with the runners" do
expect(Print).to have_received(:create).with(photo: photo_1, subject: runner)
expect(Print).to have_received(:create).with(photo: photo_2, subject: runner)
end
end
end
And all goes green. Perfect!
... Not so fast. When I run the whole test suite, depending on the seed order, I am now running into problems.
It appears that the class Print; end line can sometimes overwrite print.rb's definition of Print (which obviously inherits from ActiveRecord) and therefore fail a bunch of tests at various points in the suite. One example is:
NoMethodError:
undefined method 'reflect_on_association' for Print:Class
This makes for an unhappy suite.
Any advice on how to tackle this. While this is one example, there are numerous times where a service is directly referencing a model's method, and I've taken the above approach to stubbing them out. Is there a better way?
You don't have to create the Print class, simply use the one that is loaded, and stub it:
describe RunnerPhotos do
let(:runner) { double }
let(:photo_1) { double(id: 1) }
let(:photo_2) { double(id: 2) }
let(:run_report) { double(photos: [photo_1, photo_2]) }
before(:each) do
#service = RunnerPhotos.new(runner)
end
describe "#create_print_from_run_report(run_report)" do
before(:each) do
allow(Print).to receive(:create)
#service.create_print_from_run_report(run_report)
end
it "creates a print for every run report photo associating it with the runners" do
expect(Print).to have_received(:create).with(photo: photo_1, subject: runner)
expect(Print).to have_received(:create).with(photo: photo_2, subject: runner)
end
end
end
Edit
If you really need to create the class in the scope of this test alone, you can undefine it at the end of the test (from How to undefine class in Ruby?):
before(:all) do
unless Object.constants.include?(:Print)
class TempPrint; end
Print = TempPrint
end
end
after(:all) do
if Object.constants.include?(:TempPrint)
Object.send(:remove_const, :Print)
end
end
I appreciate the create method could arguably be abstracted into the Print model, but let's keep it as is for now.
Let's see what happens if we ignore this line.
Your difficulty in stubbing a class is a sign that the design is inflexible. Consider passing an already-instantiated object to either the constructor of MakePrintsForRunner or the method #from_run_report. Which to choose depends on the permanence of the object - will the configuration of printing need to change at run time? If not, pass to the constructor, if so, pass to the method.
So for our step 1:
class MakePrintsForRunner
def initialize(runner, printer)
#runner = runner
#printer = printer
end
def from_run_report(run_report)
run_report.photos.each do |photo|
#printer.print(photo: photo, subject: #runner)
end
end
end
Now it's interesting that we're passing two objects to the constructor, yet #runner is only ever passed to the #print method of #printer. This could be a sign that #runner doesn't belong here at all:
class MakePrints
def initialize(printer)
#printer = printer
end
def from_run_report(run_report)
run_report.photos.each do |photo|
#printer.print(photo)
end
end
end
We've simplified MakePrintsForRunner into MakePrints. This only takes a printer at construction time, and a report at method invocation time. The complexity of which runner to use is now the responsibility of the new 'printer' role.
Note that the printer is a role, not necessarily a single class. You can swap the implementation for different printing strategies.
Testing should now be simpler:
photo1 = double('photo')
photo2 = double('photo')
run_report = double('run report', photos: [photo1, photo2])
printer = double('printer')
action = MakePrints.new(printer)
allow(printer).to receive(:print)
action.from_run_report(run_report)
expect(printer).to have_received(:print).with(photo1)
expect(printer).to have_received(:print).with(photo2)
These changes might not suit your domain. Perhaps a runner shouldn't be attached to a printer for more than one print. In this case, perhaps you should take a different next step.
Another future refactoring might be for #from_run_report to become #from_photos, since the report isn't used for anything but gathering photos. At this point the class looks a bit anaemic, and might disappear altogether (eaching over photos and calling #print isn't too interesting).
Now, how to test a printer? Integrate with ActiveRecord. This is your adapter to the outside world, and as such should be integration tested. If all it really does is create a record, I probably wouldn't even bother testing it - it's just a wrapper around an ActiveRecord call.
Class names are just constants so you could use stub_const to stub an undefined constant and return a double.
So instead of defining a class in your before(:each) block do this:
before(:each) do
stub_const('Print', double(create: nil))
#service.create_print_from_run_report(run_report)
end
I'm having trouble coming up with some tests for a method I want to write.
The method is going to take a hash of some data and create a bunch of associated models with it. The problem is, I'm having a hard time figuring out what the best practice for writing this sort of test is.
For example, the code will:
Take a hash that looks like:
{
:department => 'CS',
:course_title => 'Algorithms',
:section_number => '01B'
:term => 'Fall 2012',
:instructor => 'Bob Dylan'
}
And save it to the models Department, Course, Section, and Instructor.
This will take many calls to model.find_or_create, etc.
How could I go about testing each separate purpose of this method, e.g.:
it 'should find or create department' do
# << Way too many stubs here for each model and all association calls
dept = mock_model(Department)
Department.should_receive(:find_or_create).with(:name => 'CS').and_return(dept)
end
Is there a way to avoid the massive amounts of stubs to keep each test FIRST (fast independent repeatable self-checking timely) ? Is there a better way to write this method and/or these tests? I'd really prefer to have short, clean it blocks.
Thank you so much for any help.
Edit:
The method will probably look like this:
def handle_course_submission(param_hash)
department = Department.find_or_create(:name => param_hash[:department])
course = Course.find_or_create(:title => param_hash[:course_title])
instructor = Instructor.find_or_create(:name => param_hash[:instructor])
section = Section.find_or_create(:number => param_hash[:section_number], :term => param_hash[:term])
# Maybe put this stuff in a different method?
course.department = department
section.course = course
section.instructor = instructor
end
Is there a better way to write the method? How would I write the tests? Thanks!
For passing an array of sections to be created:
class SectionCreator
# sections is the array of parameters
def initialize(sections)
#sections = sections
end
# Adding the ! here because I think you should use the save! methods
# with exceptions as mentioned in one of my myriad comments.
def create_sections!
#sections.each do |section|
create_section!(section)
end
end
def create_section!(section)
section = find_or_create_section(section[:section_number], section[:term])
section.add_course!(section_params)
end
# The rest of my original example goes here
end
# In your controller or wherever...
def action
SectionCreator.new(params_array).create_sections!
rescue ActiveRecord::RecordInvalid => ex
errors = ex.record.errors
render json: errors
end
Hopefully this covers it all.
My first thought is that you may be suffering from a bigger design flaw. Without seeing the greater context of your method it is hard to give much advice. However, in general it is good to break the method up into smaller pieces and follow the single level of abstraction principle.
http://www.markhneedham.com/blog/2009/06/12/coding-single-level-of-abstraction-principle/
Here is something you could try although as mentioned before this is definitely still not ideal:
def handle_course_submission(param_hash)
department = find_or_create_department(param_hash[:department])
course = find_or_create_course(param_hash[:course_title])
# etc.
# call another method here to perform the actual work
end
private
def find_or_create_department(department)
Department.find_or_create(name: department)
end
def find_or_create_course(course_title)
Course.find_or_create(title: course_title)
end
# Etc.
In the spec...
let(:param_hash) do
{
:department => 'CS',
:course_title => 'Algorithms',
:section_number => '01B'
:term => 'Fall 2012',
:instructor => 'Bob Dylan'
}
end
describe "#save_hash" do
before do
subject.stub(:find_or_create_department).as_null_object
subject.stub(:find_or_create_course).as_null_object
# etc.
end
after do
subject.handle_course_submission(param_hash)
end
it "should save the department" do
subject.should_receive(:find_or_create_department).with(param_hash[:department])
end
it "should save the course title" do
subject.should_receive(:find_or_create_course).with(param_hash[:course_title])
end
# Etc.
end
describe "#find_or_create_department" do
it "should find or create a Department" do
Department.should_receive(:find_or_create).with("Department Name")
subject.find_or_create_department("Department Name")
end
end
# etc. for the rest of the find_or_create methods as well as any other
# methods you add
Hope some of that helped a little. If you post more of your example code I may be able to provide less generalized and possibly useful advice.
Given the new context provided, I would split the functionality up amongst your models a little more. Again, this is really just the first thing that comes to mind and could definitely be improved upon. It seems to me like the Section is the root object here. So you could either add a Section.create_course method or wrap it in a service object like so:
Updated this example to use exceptions
class SectionCreator
def initialize(param_hash)
number = param_hash.delete(:section_number)
term = param_hash.delete(:term)
#section = find_or_create_section(number, term)
#param_hash = param_hash
end
def create!
#section.add_course!(#param_hash)
end
private
def find_or_create_section(number, term)
Section.find_or_create(number: number, term: term)
end
end
class Section < ActiveRecord::Base
# All of your current model stuff here
def add_course!(course_info)
department_name = course_info[:department]
course_title = course_info[:course_title]
instructor_name = param_hash[:instructor]
self.course = find_or_create_course_with_department(course_title, department_name)
self.instructor = find_or_create_instructor(instructor_name)
save!
self
end
def find_or_create_course_with_department(course_title, department_name)
course = find_or_create_course(course_title)
course.department = find_or_create_department(department_name)
course.save!
course
end
def find_or_create_course(course_title)
Course.find_or_create(title: course_title)
end
def find_or_create_department(department_name)
Department.find_or_create(name: department_name)
end
def find_or_create_instructor(instructor_name)
Instructor.find_or_create(name: instructor_name)
end
end
# In your controller (this needs more work but..)
def create_section_action
#section = SectionCreator.new(params).create!
rescue ActiveRecord::RecordInvalid => ex
flash[:alert] = #section.errors
end
Notice how adding the #find_or_create_course_with_department method allowed us to add the association of the department in there while keeping the #add_course method clean. That is why I like to add those methods even though they sometimes seem superflous like in the case of the #find_or_create_instructor method.
The other advantage of breaking out the methods in this fashion is that they become easier to stub in tests as I showed in my first example. You can easily stub all of these methods to make sure the database isn't actually being hit and your tests run fast while at the same time guarantee through the test expectations that the functionality is correct.
Of course, a lot of this comes down to personal preference on how you want to implement it. In this case the service object is probably unnecessary. You could just as easily have implemented that as the Section.create_course method I referenced earlier like so:
class Section < ActiveRecord::Base
def self.create_course(param_hash)
section = find_or_create(number: param_hash.delete(:section_number), term: param_hash.delete(:term))
section.add_course(param_hash)
section
end
# The rest of the model goes here
end
As to your final question, you can definitely stub out methods in RSpec and then apply expectations like should_receive on top of those stubs.
It's getting late so let me know if I missed anything.
Note: I'm only posting this question so that others may find it if they ever need to, as I have found a good solution.
In my controller tests, I don't want to commit to the database, but I still want the controllers to use finder methods to get mock objects (by mocking the find method), but then I want to use dom_id on those mocks in assert_select, to verify that they're being displayed.
However, since they're non-saved objects, dom_id keeps returning new_object instead of object_1, object_2, et cetera.
Is there any quick way to get it to work? I really don't want to persist real records in tests.
Suppose you're using ActiveSupport::TestCase and FactoryGirl for building model objects, you can add this to your test/test_helper.rb file:
# Generate model with id - useful for assert_select with dom_id
def model(name, options = {})
#next_id = {} if #next_id.nil?
#next_id[name] = 1 unless #next_id.has_key(name)
m = build(name, options)
m.id = #next_id[name]
#next_id[name] += 1
m
end
Then, suppose you're using RR as your mocking library, you can do something like this:
test 'something' do
posts = [model(:post), model(:post)]
mock(Post).all { posts }
get :index
assert_select "##{dom_id(posts.first)}", posts.first.title
end
Consider the following class and methods: (This class is obviously much more complete, but for the sake of this thread...):
class Order < ActiveRecord::Base
def check
if (self.user.phone == "55555555") do
self.a_certain_method
return
end
end
def a_certain_method
# Real implementation goes here
end
end
And the following Unit Test:
describe :do_route do
it "should call a_certain_method if user phone number matches 55555555" do
# Create a user
user = Factory(:user)
# Set hard-coded phone number
user.phone = "55555555"
user.save!
# Create an order made by the ordering user
order = Factory(:order, :ordering_user => user)
# Set expectation for a "a_certain_method" call
mock(order).a_certain_method
# Call the tested method
order.check
end
end
From some reason, the above test produces an RR::Errors::TimesCalledError error, which claims that a_certain_method was called 0 times instead of 1... I've been searching around the web for a solution with no luck.
I've tried building a similiar test on a non-activerecord class, and the test produces no errors.
I've used the debugger to check that it does reach the self.a_certain_method line, and also tried using the following instead of mock(order).a_certain_method:
any_instance_of(Order) do |o|
mock(o).a_certain_method
end
Does anyone have any idea how to solve this issue since i'm kind of desperate...
I figured out what the problem was, it failed since the number was already in the database. so it failed to save the hard coded user.phone change.
Thanks for the help though :)