Search code examples
rubyminitestruby-mocha

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


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.


Solution

  • 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: