Mocking Enumerable in RSpec
Rails puts some really powerful mocking libraries at our disposal. As long as your models are backed by ActiveRecord, ActiveRecord Fixtures and Factory Bot are two very good libraries for mocking. More often than not, these cover the use cases for most applications. But when your models are not backed by ActiveRecord, there’s an excellent test-double framework for rspec, called RSpec Mocks that you can use.
Basic Mocking with RSpec Mocks
Basic mocking using RSpec Mocks is straight forward. Initialize the mock object as below
@mock_post = double('post')
and then
allow(@mock_post).to receive(:title) { 'sample title' }
Mocking a chain of methods
For mocking a chain of methods, there are two options.
Lets say that the Post
object has a method author
, that returns another object of class
Author
. The Author
class has multiple methods, including name
and image
If your code only ever calls the name
method for the post’s author, you can easily setup
this mock object as
allow(@mock_post).to receive_message_chain(:author, :name) { 'Sample Author' }
If you need a more complex mocking, you can have 2 mock objects
@mock_author = double('author')
allow(@mock_author).to receive(:name) { 'Sample Author' }
allow(@mock_author).to receive(:image) { 'https://sample.image' }
allow(@mock_post).to receive(:author) { @mock_author }
Mocking Enumerable
In my specific project, I had a model that responded to each
, and also had other methods
that needed mocking, total
, skip
, limit
etc. My initial instinct was that I could
just do something like
posts = double('post')
allow(posts).to receive(:each) { posts_list }
But unfortunately that doesn’t work, because each expects a block and yields each of the list item to the block. Fortunately, RSpec Mocks has a method and_yields that lets us set up just what we need.
Continuing with the above example, lets say we need to have 10 mock posts. We can set it up as,
posts_list = []
10.times do |index|
post = double('post', title: "post_#{index}")
posts_list << post
end
posts = double('post', total: posts_list.size, skip: 0, limit: posts_list.size)
iterator = allow(posts).to receive(:each)
posts_list.each do |post|
iterator.and_yield(post)
end
At this point, the posts
mock object responds to the each
iterator and returns the
10 mock post objects. It also responds to the total
, skip
and limit
methods correctly.
Hope this helps.