How to stub class and instance methods of the same class using rspec/rspec-mocks

5,313 views
Skip to first unread message

JSchlinker

unread,
Jun 25, 2015, 6:23:44 AM6/25/15
to rs...@googlegroups.com

I am using rspec-mock for test-driven-development.

I am starting implementing a single class and mocking/stubbing the other classes using rspec-mock. Mocking only objects of classes yet to be implemented works well. However when I try to mock a class method and instance methods for objects of the same class I am not sure how the preferred way to do this looks like.

I have code that e.g. looks like this:

new_book = Book.new
content
= new_book.read_page(5)


I now want to mock out everything that has to do with the class "Book". In this example this would mean "Book.new" and "new_book.read_page"

Would I first mock the object using

book_object = double("Book")
allow
(book_object).to receive(:read_page).and_return(...)


and then mock the class by 

book = class_double("Book")
allow
(book).to receive(:new).and_return(book_object)


Is this the way to go?

Myron Marston

unread,
Jun 25, 2015, 11:27:23 AM6/25/15
to rs...@googlegroups.com

Would I first mock the object using

book_object = double("Book")
allow(book_object).to receive(:read_page).and_return(...)

That certainly works, but I have two suggestions:

  • Use instance_double("Book") instead of double("Book"). The latter is not a verifying double, which means it does nothing to warn you when the interface of your test double and that of Book instances diverge.
  • Since you don’t appear to care about the arguments, you can in-line the stub as part of the test double definition:
book_object = instance_double("Book", read_page: page_value)

and then mock the class by

book = class_double("Book")
allow(book).to receive(:new).and_return(book_object)

Again, you can inline the new stub:

book = class_double("Book", new: book_object)

Actually, you could inline it all into one line:

book = class_double("Book", new: instance_double("Book", read_page: page_value))

That said, neither your snippet nor this one-line will work for the code snippet you pasted, because your snippet directly references the Book constant.

One solution is to change the code under test to accept an injectable book_factory argument (which can have a default value of Book but in your test you can pass the class double for it). This makes has all the benefits of dependency injection (makes the dependency explicit in the method signature, supports additional flexibility by allowing the caller to pass a different book implementation, etc) while having all the downsides of dependency injection (added code, an additional argument that is just there to support testing, YAGNI, etc).

Another approach is to chain as_stubbed_const off your class_double:

class_double("Book", new: instance_double("Book", read_page: page_value)).as_stubbed_const

This will cause the Book constant to be defined (or replaced) for the duration of the example as your class double, so that any code that references the Book constant will get your test double rather than the real Book class and/or an “undefined constant” error.

A third approach is to define your Book class and stub the new method on that directly…but it sounds like you are wanting to avoid defining it so one of the first two approaches better fits the workflow you are aiming for, I think.

HTH,
Myron


--
You received this message because you are subscribed to the Google Groups "rspec" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rspec+un...@googlegroups.com.
To post to this group, send email to rs...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/rspec/0690a743-805e-410a-ba9d-2bc2a2ea930e%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

JSchlinker

unread,
Jun 26, 2015, 5:10:51 AM6/26/15
to rs...@googlegroups.com
Works perfectly, thanks!
I will go with the "as_stubbed_const"-approach as it suits my needs best for now.

J.
Reply all
Reply to author
Forward
0 new messages