How do I verify the number of elements and content of an array using ParameterMatchers?

232 views Asked by At

I am learning Ruby with TDD using Mocha and MiniTest. I have a class that has one public method and many private methods, so the only method my tests are going to tests are the public one.

This public method does some processing and creates an array which is sent to another object:

def generate_pairs()
        # prepare things
        pairs = calculate_pairs()

        OutputGenerator.write_to_file(path, pairs)
    end

Great. To test it, I would like to mock the OutputGenerator.write_to_file(path, pairs) method and verify the parameters. My first test I could sucessfully implement:

def test_find_pair_for_participant_empty_participant

      available_participants = []

      OutputGenerator.expects(:write_to_file).once.with('pairs.csv', [])
      InputParser.stubs(:parse).once.returns(available_participants)

      pair = @pairGenerator.generate_pairs

    end

Now I would like to test with one pair of participants. I am trying this

 def test_find_pair_for_participant_only_one_pair

      participant = Object.new
      participant.stubs(:name).returns("Participant")
      participant.stubs(:dept).returns("D1")

      participant_one = Object.new
      participant_one.stubs(:name).returns("P2")
      participant_one.stubs(:dept).returns("D2")


      available_participants = [participant_one]

      OutputGenerator.expects(:write_to_file).once.with('pairs.csv', equals([Pair.new(participant, participant_one)])) # here it fails, of course
      InputParser.stubs(:parse).once.returns(available_participants)
      @obj.stubs(:get_random_participant).returns(participant)

      pair = @obj.generate_pairs

    end

The problem is that equals will only match the obj reference, not the content.

Is there any way I can verify the content of the array? Verifying the number of elements inside the array would also be extremely useful.

ps: I am sorry if the code doesn't follow ruby standards, I am doing this project to learn the language.

1

There are 1 answers

1
Matt Sanders On BEST ANSWER

What you are testing here demonstrates a kind of hard coupling. That is your primary class is always dependent on OutputGenerator which makes testing your outputs tricky and can lead to a lot of pain if/when you have to refactor your designs.

A good pattern for this is dependency injection. With this you can just write a temporary ruby object you can use to evalute the output of your function however you want:

# in your main class...

class PairGenerator

  def initialize(opts={})
    @generator = opts[:generator] || OutputGenerator
  end

  def generate_pairs()
    # prepare things
    pairs = calculate_pairs()

    @generator.write_to_file(path, pairs)
  end

end

# in the test file...

# mock class to be used later, this can be at the bottom of the
# test file but here I'm putting it above so you are already
# aware of what it is doing
#
class MockGenerator
  attr_reader :path, :pairs

  def write_to_file(path, pairs)
    @path = path
    @pairs = pairs
  end
end

def test_find_pair_for_participant_only_one_pair

  participant = Object.new
  participant.stubs(:name).returns("Participant")
  participant.stubs(:dept).returns("D1")

  participant_one = Object.new
  participant_one.stubs(:name).returns("P2")
  participant_one.stubs(:dept).returns("D2")

  available_participants = [participant_one]

  # set up a mock generator
  mock_generator = MockGenerator.new

  # feed the mock to a new PairGenerator object as a dependency
  pair_generator = PairGenerator.new(generator: mock_generator)

  # assuming this is needed from your example
  pair_generator.stubs(:get_random_participant).returns(participant)

  # execute the code
  pair_generator.generator_pairs

  # output state is now captured in the mock, you can evaluate for
  # all the test cases you care about
  assert_equal 2, mock_generator.pairs.length
  assert mock_generator.pairs.include?(participant)
end

Hope this helps! Dependency Injection is not always appropriate but it is great for cases like this.

Some other posts about the use of dependency injection you might find helpful: