I have the following controller test case:
def test_showplain
Cleaner.expect(:parse).with(#somecontent)
Cleaner.any_instance.stubs(:plainversion).returns(#returnvalue)
post :showplain, {:content => #somecontent}
end
This works fine, except that I want the "stubs(:plainversion)" to be an "expects(:plainversion)".
Here's the controller code:
def showplain
Cleaner.parse(params[:content]) do | cleaner |
#output = cleaner.plainversion
end
end
And the Cleaner is simply:
class Cleaner
### other code and methods ###
def self.parse(#content)
cleaner = Cleaner.new(#content)
yield cleaner
cleaner.close
end
def plainversion
### operate on #content and return ###
end
end
Again, I can't figure out how to reliably test the "cleaner" that is made available from the "parse" method. Any suggestions?
This is a little tricky. The easiest a approach will be to break the problem into two pieces: the testing of the controller and the testing of the controller.
You have the testing of the controller set-- just remove your expectation around plainversion call.
Then, separately, you want to test the Cleaner.parse method.
cleaner = Cleaner.new('x');
cleaner.expects :close
Cleaner.expects(:new).returns(cleaner)
called_with = nil
Cleaner.parse('test') do |p|
called_with = p
end
assert_equal p, cleaner
That is not very clear what's going on. Makes me think that there is a simpler version of this. Can cleaner just be a simple function that takes a string and returns another one? Skip all the yielding and variable scoping? That will be much easier to test.
You might find the documentation for Mocha::Expectation#yields useful.
I've made an attempt at showing how you might do what you want in this gist. Note that I had to tweak the code a bit to get it into a self-contained runnable test.
Related
How could I write a test to find the last created record?
This is the code I want to test:
Post.order(created_at: :desc).first
I'm also using factorybot
If you've called your method 'last_post':
def self.last_post
Post.order(created_at: :desc).first
end
Then in your test:
it 'should return the last post' do
expect(Post.last_post).to eq(Post.last)
end
On another note, the easiest way to write your code is simply
Post.last
And you shouldn't really be testing the outcome of ruby methods (you should be making sure the correct ruby methods are called), so if you did:
def self.last_post
Post.last
end
Then your test might be:
it 'should send the last method to the post class' do
expect(Post).to receive(:last)
Post.last_post
end
You're not testing the outcome of the 'last' method call - just that it gets called.
The accepted answer is incorrect. Simply doing Post.last will order the posts by the ID, not by when they were created.
https://apidock.com/rails/ActiveRecord/FinderMethods/last
If you're using sequential IDs (and ideally you shouldn't be) then obviously this will work, but if not then you'll need to specify the column to sort by. So either:
def self.last_post
order(created_at: :desc).first
end
or:
def self.last_post
order(:created_at).last
end
Personally I'd look to do this as a scope rather than a dedicated method.
scope :last_created -> { order(:created_at).last }
This allows you to create some nice chains with other scopes, such as if you had one to find all posts by a particular user/account, you could then chain this pretty cleanly:
Post.for_user(user).last_created
Sure you can chain methods as well, but if you're dealing with Query interface methods I feel scopes just make more sense, and tend to be cleaner.
If you wanted to test that it returns the correct record, in your test you could do something like:
let!(:last_created_post) { factory_to_create_post }
. . .
it "returns the correct post"
expect(Post.last_post).to eq(last_created_post)
end
If you wanted to have an even better test, you could create a couple records before the last record to verify the method under test is pulling the correct result and not just a result from a singular record.
I know some of you are already doubting my sanity with this. I have a ActiveRecord class that uses method missing to dig inside a JSON attribute it has.
# app/models/request_interactor.rb
...
def method_missing(method_sym, *arguments, &block)
return self.request_params[method_sym.to_s] if self.request_params[method_sym.to_s]
super
end
the test looks like this
before(:each) do
#ri = RequestInteractor.create(result: {magic_school: true, magic_learnt: 'all things magical'}, request_params: {application_id: 34, school_id: 20, school_name: 'Hogwarts', course_name: 'Defence against the Dark Arts.'})
end
it 'should respond to attributes set in the request parameters' do
expect(#ri).to respond_to(:school_name)
expect(#ri.school_name).to eq('Hogwarts')
end
I tried binding inside the test, the #ri.school_name will eq 'Hogwarts', but when it runs the responds_to it will fail saying there is no such a method! The dirty, dirty liar!
I tried doing something like this in the model:
def respond_to?(method, include_private = false)
super || self.respond_to?(method, include_private)
end
But this will return a stack level too deep, because of recursion, because of recursion.. so now the fate of my day is in your hands! Enlighten me O' great ones. how would I test the respond to of the method missing.
Use respond_to_missing. More infos here.
Now, with all this being said. Your pattern will still look hackish if you ask me.
Refactors
Ruby has tons of way to clean this.
Use a delegation pattern
delegate :method_name, :to => :request_params
(check other options in doc). This should solve your problems by having a method in your object so respond_to? will work and you will avoid overriding method_missing.
Generate your access methods when setting request_params (meta-programming your accessors).
Use OpenStruct since these can be initialized with a Hash such as your request_params. If you add delegation on top, you should be cool.
Hope this helps.
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.
I am doing something similar to these railscast episodes:
http://railscasts.com/episodes/165-edit-multiple
http://railscasts.com/episodes/52-update-through-checkboxes
The problem is that those are only trying to modify the selected models. I need to update every single model.
The first thing I found out is that id not in () does not give back everything like I expected so I had to make a special case for the empty list.
This code works, but it doesn't seem very DRY. At the very least I should be able to merge the normal case into one line.
def update_published
if params[:book_ids].empty?
Book.update_all(published: false)
else
Book.where(id: params[:book_ids]).update_all(published: true)
Book.where("id not in (?)", params[:book_ids]).update_all(published: false)
end
redirect_to books_path
end
Any ideas for improvement would be appreciated.
Why not just do the following:
def update_published
Book.update_all(published: false)
Book.where(id: params[:book_ids]).update_all(published: true)
redirect_to books_path
end
It'll be faster, and it's pretty straightforward and clean.
I finally figured it out.
def update_published
Book.update_all(["published = id in (?)", params[:book_ids]])
redirect_to books_path
end
I was trying to do something similar yesterday, but it kept giving me an error where the ? wasn't filled out and it was doing a where on my ids. Today, I finally realized I needed to wrap both parameters into an array.
One strange caveat about this is it messed up some of my specs. I was checking for false, but it was giving nil. In the database it seems to be set to false though. I changed my spec from be(false) to be_false and feel pretty safe now.
You could do it like this:
def update_published
Book.transaction do
Book.update_all(published: false)
Book.scoped.find(params[:book_ids]).update_all(published: true) #Should be Lazyloaded. Testing Now
end
end
Since it is a transaction it will be pretty fast. Also note how i use "find", and not "where". It is nicer in my opinion. Makes for slightly cleaner code.
NOTE: I would though question why you need to update every single book entity each time. Would it not be smarter to keep proper track of the published books one by one? You are bound to run into troubles if you need to pass every book ID in each time you want to update one single book. This solution isn't very scalable.
What you should have is this:
def publish_book(book)
book.published = true;
book.save!
end
Or even better:
#Book.rb
def publish
self.published = true
self.save #not 100% sure you need this. Anyone?
end
Whenever you can you (many agree) should follow the "skinny controller, fat model" approach. Meaning that you basicly put as much of the code inside the model class, and not anywhere else whenever you can spare it.
The application I am writing is pretty complex and has different stylesheets/javascripts depending on the action. Right now I have added some methods to the application controller that allows me to build up an array which then is used in the application.html.erb layout. This feels a little sloppy. It would be nice to somehow configure this from a settings file based on action or something. (I guess that might get messy too though)
Any ideas?
def initialize
super()
#application_stylesheets = Array.new
#application_javascripts = Array.new
end
def add_stylesheet(stylesheet)
#application_stylesheets.push(stylesheet)
end
def add_javascript(javascript)
#application_javascripts.push(javascript)
end