I just started using mocha and I find it annoying that when creating a new mock object, mocha expects it to be called exactly once. I have helper methods to generate my mocks and I'm doing something like this
my_mock = mock(HashOfParameters)
All of the parameters might not get called for each test method so it will raise an error:
expected exactly once, not yet invoked
So I figured I needed to do something like this:
my_mock = mock()
HashOfParameters.each do |k, v|
my_mock.expects(k).returns(v).at_least(0)
end
This works but I was wondering if there was an easier way to do this, like changing a default configuration somewhere...
Ok, that was a stupid question... I hadn't took the time to truly understand the difference between a mock and a stub. Here's a good article that shows how it works :
http://martinfowler.com/articles/mocksArentStubs.html
So in my example, I should have been using the stub method instead of mock.
Related
Using Mocha on Rails 4.2.
I'm testing a method that it should make a call to another method with the correct parameters. These parameters are ActiveRecord objects that it calls up from the database. Here is the key line in my test:
UserMailer.expects(:prompt_champion).with(users(:emma), [[language, 31.days.ago]]).once
Both users(:emma) and language are ActiveRecord objects.
Even though the correct call is made, the test fails because the parameters don't match the expectations. I think this might be because it's a different Ruby object each time a record is pulled up from the database.
I think one way around it is to see what method is being used in my code to pull up the records and stub that method to return mocks, but I don't want to do this because a whole bunch of Records are retrieved then filtered down to get to the right one, mocking all those records would make the test way too complex.
Is there a better way of doing this?
You could use block form of allow/expect.
expect(UserMailer).to receive(:prompt_champion) do |user, date|
expect(user.name).to eq "Emma"
expect(date).to eq 31.days.ago # or whatever
end
Sergio gave the best answer and I accepted it. I discovered the answer independently and found out along the way that I needed to return a mock from the ActionMailer method to make everything work properly.
I think it best to post here my complete test here for the sake of any other hapless adventurer to come this way. I'm using Minitest-Spec.
it 'prompts champions when there have been no edits for over a month' do
language.updated_at = 31.days.ago
language.champion = users(:emma)
language.save
mail = mock()
mail.stubs(:deliver_now).returns(true)
UserMailer.expects(:prompt_champion).with do |user, languages|
_(user.id).must_equal language.champion_id
_(languages.first.first.id).must_equal language.id
end.once.returns(mail)
Language.prompt_champions
end
You could use an RSpec custom matcher and compare expected values in that function.
I found RailsCasts episode, and used this logic and code samples for my needs.
But one thing bothers me.
constraint looks like:
constraints(Subdomain.new) do ... end
which uses this code:
class Subdomain
def matches?(request)
....
end
end
end
And it works.
But I don't get two things. First, I do not invoke matches? anywhere, why this method is just executed on initializing Subdomain.new?
Second concern. I don't pass any parameter, but it somehow assigns request argument to actual rack request and it just works.
For example, I didn't like this syntax:
constraints(Subdomain.new) do ... end
so I decided to make it module with method subdomain(request), but as made it module, it started raising wrong number or arguments error (0 for 1).
I found out that method matches? is defined in mapper.rb, may be it is called somewhere backwards in rails, but this way it should be overwritten by my subdomain file? If not, as my matches is class method, how it works without any Subdomain instance to which it is applied?
As I said, everything works fine, but I would like to understand what exactly happens, because I don't like using something that appears david blane magic code to me.
Reading some source code of Rails mapper module didn't give me understanding.
Well, little more of reading source code gave me a clue. I found one more matches?, defined for #constraints
def matches?(req)
#constraints.all? do |constraint|
(constraint.respond_to?(:matches?) && constraint.matches?(req)) ||
(constraint.respond_to?(:call) && constraint.call(*constraint_args(constraint, req)))
end
end
So for every constraint it checks if it responds to matches? and then invokes it with rack request argument.
Using MiniTest::Spec and Mocha:
describe "#devices" do
it "scopes the devices by the provided :ip_list" do
ips = 'fast tests ftw!'
ds = DeviceSearch.new ip_list: ips
Device.expects(:scope_by_ip_list).once.with(ips)
ds.devices
end
end
When I make the code work correctly, this test will fail, because calling Device.expects(:scope_by_ip_list) also stubs Device.scope_by_ip_list, and since I don't specify a .returns(Devices.scoped) or some such, it stubs out the method with nil. So, in my code which properly scopes a list of devices and then does further operations, the further operations blow up.
I don't want to have to specify a .returns parameter, though, because I totally don't care what it returns. I don't want to stub the method at all! I just want to set up an expectation on it, and leave it functioning just the way it is.
Is there a way to do that?
(To me, it seems very awkward to say something like Device.expects(:foo).returns('bar')—when I say that Model expects method, I'm not saying to stub that method! We can say Device.stubs(:foo), if we want to stub it.)
The behavior is intended and can't be changed. Look at the following post to see how it can be circumwented:
rspec 2: detect call to method but still have it perform its function
I'm attempting to test a class which makes use of the rails configuration file. I'd like to mock Rails::configuration.
I've tried things like
Rails::singleton_class.expects(:configuration).returns('result')
Rails::singleton_class.stubs(:configuration).returns('result')
How do I go about doing this?
Rails.expects(:configuration).returns('result')
Please note there was a typo in your example. The returned value must be passed using returns, not return.
Also note, Rails.configuration returns Rails.application.config. If your method doesn't use Rails.configuration directly, it might actually bypass the call and your expectation won't work.
Rails.stubs(:configuration).returns(Rails::Application::Configuration.allocate)
This answer on mocking a Net response
helped
I want to discover with BDD missing :include params for ActiveRecord::Base.find method. So my idea is to have in spec something like this:
ActiveRecord::Base.should_receive(:find).once.and_proxy_to_original_method
parent = SomeClass.find 34
parent.child.should be_loaded
parent.other_children.should be_loaded
If #child or #other_children associations are not eager loaded, expectation should fail with something like:
"Expected ActiveRecord::Base.find to be invoked once but it was invoked 2 more times with following args: 1. ...; 2. ..."
Does anyone know if there's some matcher that works like this or how to make this.
Thanks
I think I had the same problem here. In your particular case I would do this which I find quite clean.
original_method = ActiveRecord::Base.method(:find)
ActiveRecord::Base.should_receive(:find).once do (*args)
original_method.call(*args)
end
I believe you could extend the Rspec Mocks::MessageExpectation class to include the and_proxy_to_original_method method, shouldn't be too hard, but I haven't looked.