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)
Would I first mock the object using
book_object = double("Book")
allow(book_object).to receive(:read_page).and_return(...)
book = class_double("Book")
allow(book).to receive(:new).and_return(book_object)
Is this the way to go?
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:
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.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.