Testing objects that create other objects

I come across this problem quite a lot when testing my objects.

Most recently, during my SudokuValidator attempt (Outside-In with Aruba/RSpec for SudokuValidator Exercise by danscotton · Pull Request #8 · thoughtbot/sudoku_validator · GitHub), @joelq has given me some great feedback where my FileParser object could create and return an instance of Puzzle†. I agree this improves the design of the code, and I can actually test it quite easily because Puzzle is a value object. This means I can write my assertion in RSpec like:

puzzle = Puzzle.new(...)
expect(FileParser.new(input).parse_rows).to eq puzzle

However! If Puzzle wasn’t a value object, and actually had some state, how would you test that FileParser creates an instance of Puzzle? Or for that matter, any object creates another object (a factory perhaps)…

RSpec gives us any_instance_of which would allow me to do:

expect_any_instance_of(Puzzle).to receive(:new).with(...)

but I’m unsure whether this is a bit of a code smell? Here, I’m asserting that an actual Puzzle object should be sent the message :new (as opposed to an object that implements the Puzzle role receiving :new).

Sooo…I guess my question is, is any_instance_of a code smell and do you guys use it? Or, is there a better way to this?

Outside-In with Aruba/RSpec for SudokuValidator Exercise by danscotton · Pull Request #8 · thoughtbot/sudoku_validator · GitHub

@danscotton, any_instance_of is for messages sent to instances, however here you are checking that :new is sent to the Puzzle class.

When testing against an object’s type, you can ask it directly rather than asserting that a class received :new. Ruby objects respond to instance_of?. For example:

'abcd'.instance_of? String
# => true

'abcd'.instance_of? Fixnum
# => false

This allow us to test as follows:

puzzle = FileParser.new(input).parse_rows
expect(puzzle.instance_of?(Puzzle)).to be_true

RSpec automatically creates matchers for methods ending in ? by prepending be_. This gives us the somewhat cleaner:

parser = FileParser.new(input)
expect(parser.parse_rows).to be_instance_of Puzzle