Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Help - how to mock 'deep' methods? #55

Open
inspire22 opened this issue Mar 21, 2014 · 5 comments
Open

Help - how to mock 'deep' methods? #55

inspire22 opened this issue Mar 21, 2014 · 5 comments

Comments

@inspire22
Copy link

I've been a fan of rr, but have been unable to use it for much besides unit tests (since the object is already created for those).

For example, my controller calls this

@group.group_items.joins(:item).where('user_id=?', @me).destroy_all

I want to create an expectation that this method is called, since it some cases it seems to not be happening. Any suggestions how to do something this convoluted? Ultimately should I be mocking "ActiveRecord::Associations::CollectionProxy", as that seems crazy...

@inspire22 inspire22 changed the title Help - how to mock 'deep Help - how to mock 'deep' methods? Mar 21, 2014
@mcmire
Copy link
Contributor

mcmire commented Mar 21, 2014

Can you expound, please?

@inspire22
Copy link
Author

Sorry, fat-fingered return while in the title, so github posted it early! :)

@mcmire
Copy link
Contributor

mcmire commented Mar 21, 2014

So... these sorts of things are all too common in ActiveRecord. Because of how painful it's going to be using any mocking library, I would actually not mock this and instead use a more functional test to test that this query is doing the right thing. (I would also move it to the model layer that way you are also testing it on the model layer and not the controller level)

However if you really want to mock this then what you basically need to do is mock every link in the chain. I'm assuming that @group is obtained by finding a Group and @user is obtained by finding a User? If so then it'll work something like this:

mock_user = stub!
stub(User).find { mock_user }

mock_relation2 = mock!.destroy_all
mock_relation1 = mock!.where('user_id=?', mock_user) { mock_relation1 }
mock_items = mock!.joins(:item) { mock_relation1 }
mock_group = mock!.group_items { mock_items }
stub(Group) { mock_group }

The missing step for you, I think, is mock! which simply creates a mock object instead of partially mocking an existing object. This might make it clearer:

mock_user = Object.new
stub(User).find(some_id) { mock_user }

mock_relation2 = Object.new.tap do |object|
  mock(object).destroy_all
end
mock_relation1 = Object.new.tap do |object|
  mock(object).where('user_id=?', mock_user) { mock_relation1 }
end
mock_items = Object.new.tap do |object|
  mock(object).joins(:item) { mock_relation1 }
end
mock_group = Object.new.tap do |object|
  mock(object).group_items { mock_items }
end
stub(Group).find { mock_group }

Anyway, as you can see, this sort of thing is super unwieldy and is oftentimes not worth the trouble. But that's up to you to decide :)

@inspire22
Copy link
Author

Wow, thanks so much for the deep reply, I appreciate it.

Too bad we can't do something like this:

mock Group, "find", "group_items", "joins", "where", "destroy_all"

Which could create a chain of anonymous stubs automatically & expect them to get called.. Or even making it do the splitting, which gets really elegant:

mock Group, "find.group_items.joins.where.destroy_all"

I've been writing some angularjs lately and was hoping to bring their testing ideas back into my rails programming, but models in angular are always such short chains they're easier to mock...

@mcmire
Copy link
Contributor

mcmire commented Mar 21, 2014

Yeah, RSpec actually has a method similar to what you're talking about called receive_message_chain that would work in your case like this:

expect(Group).to receive_message_chain('find.group_items.joins.where.destroy_all')

Note that if you were interested in the arguments passed to these methods, you'd still have to make separate assertions to check for that. So I'm not sure you'd gain that much.

Still, I suppose, RR could do something similar, and at the same time also allow you to keep expectations on individual method calls:

mock_user = stub!
mock(User).find(some_id) { mock_user }
mock_chain(Group)
  .find(some_id)
  .group_items
  .joins(:items)
  .where('user_id=?', mock_user)
  .destroy_all

I didn't mention this before, but you can actually kind of get something like that today:

mock_user = stub!
mock(User).find(some_id) { mock_user }
mock(Group).find(some_id) do
  mock!.group_items.
  mock!.joins(:items).
  mock!.where('user_id=?', mock_user).
  mock!.destroy_all
end

That is, mock! can either be called standalone to produce a double object, or it can be added after a method you are doubling to insert a new double object within the chain. The reason I didn't mention is it because 1) it's the same number of lines as the solution I posted before and 2) it's actually kind of unreadable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants