How do I test this code? Mocks? Stubs?

Wondering if someone can guide me on how I might test this code. For me, testing is unexplored terrain, and I’m a little baffled at whether I should use mocks, stubs, or what-have-you.

class BetaWhitelistInterceptor
    DOMAIN_WHITELIST = [
        'foobar.com',
        'boofar.net'
    ]

    def self.delivering_email(message)
        Rails.logger.info("BETA SERVER DETECTED. Filtering e-mails through whitelist.")
        filtered_to_emails = []
        message.to.each do |to|
            domain = to.split("@")[1]
            filtered_to_emails << to if DOMAIN_WHITELIST.include?(domain)
        end
        message.to = filtered_to_emails
        message.subject = "BETA - #{message.subject}"
        message.perform_deliveries = false if message.to.empty? 
    end
end

Here’s what I have so far for a test case:

class BetaWhitelistInterceptorTest < ActiveSupport::TestCase
    test "interceptor filters to-emails" do
        message = Minitest::Mock.new
        # magic goes here???
        BetaWhitelistInterceptor.delivering_email(message)
    end
end

I am very lost as to how I would use mocks/stubs in this case. I pretty much just want to test that this method is filtering e-mails based on a whitelist.

It seems like I have to know what class message is going to be, before I can appropriately mock/stub it. I’d like to avoid that if possible, and just use plain objects if I can.

Personally I try to avoid mocks/stubs/etc unless absolutely necessary (3rd party services, etc). The number of times I have seen tests pass and say everything is all good only for it to crash and die in production because of mocks I cannot count.

There is no guarantee your mock is acting like the real code. Therefore you may code the mock to act differently by accident or even if you get it right future changes to the real code may cause it to drift from the behavior you have mocked.

I prefer black-box testing that uses mocks as least as possible. In this case, I would use one of the emails in the system as an example. Have it be sent to a white-list domain and make sure it passes the message through just fine. Then send it to a blacklist domain and make sure it modifies the message as you desire.

The only downside to this approach is if your “example” email ever goes away or changes it might break your test. But that will be obvious when your test suite runs and you can then make adjustments to match the changes or choose a new example.

But now you have more confidence that not only is your interceptor acting like it should you also have confidence it is properly registered with Rails and active. Mocks won’t give you that.

I’d extract the filtering logic and test that separately. Now it is just a simple function

def self.filter_emails(emails)
  #returns filtered list
end
def self.delivering_email(message)
    message.to = filter_emails(message.to)
    message.subject = "BETA - #{message.subject}"
    message.perform_deliveries = false if message.to.empty? 
end

filter_emails is now easy to test as a standalone function

as to delivering_email - you probably just want to test that it actually delivers an email. Check the rails guides on that

This is how I’d do it in Rspec, not sure about minitest or whatever. I did make a slight change to your #delivering_email to return the message so I could test it directly without mocks. And since I can’t see the implementation of your Message class I used an OpenStruct.


class BetaWhitelistInterceptor
  DOMAIN_WHITELIST = [
    'foobar.com',
    'boofar.net'
  ]

  def self.delivering_email(message)
    Rails.logger.info("BETA SERVER DETECTED. Filtering e-mails through whitelist.")
    filtered_to_emails = []

    message.to.each do |to|
      domain = to.split("@")[1]
      filtered_to_emails << to if DOMAIN_WHITELIST.include?(domain)
    end

    message.to = filtered_to_emails
    message.subject = "BETA - #{message.subject}"
    message.perform_deliveries = false if message.to.empty?
    message
  end
end

RSpec.describe BetaWhitelistInterceptor, type: :model do
  describe ".delivering_email" do
    it 'filters non whitelisted domains' do
      message = OpenStruct.new to: ['user@foobar.com', 'user@boofar.net', 'other@bad-domain.com']

      message = BetaWhitelistInterceptor.delivering_email(message)

      expect(message.to).to match_array(['user@foobar.com', 'user@boofar.net'])
    end

    it 'assigns the correct subject' do
      message = OpenStruct.new to: ['user@foobar.com'], subject: 'test subject'

      BetaWhitelistInterceptor.delivering_email(message)

      expect(message.subject).to eq("BETA - test subject")
    end

    it 'doesnt perform deliveries without whitelisted recipients' do
      message = OpenStruct.new to: ['other@bad-domain.com'], perform_deliveries: true

      message = BetaWhitelistInterceptor.delivering_email(message)

      expect(message.to).to match_array([])
      expect(message.perform_deliveries).to be false
    end
  end
end