user1519240
user1519240

Reputation: 2354

How to avoid overtesting of a private method's behavior?

Given that:

Suppose I have the following code:

module RedisHelper
  private

  def key_for(id)
    [prefix, id].join('-')
  end

  def prefix
    self.class.name.split('::').last.underscore
  end
end
class X
  include RedisHelper

  def perform(id)
    key = key_for(id)
    redis.sadd(key, [:bar])
  end
end
class Y
  include RedisHelper

  def perform(id)
    key = key_for(id)
    redis.set(key, :foo)
  end
end

What would be the best way to test the behavior of key_for/prefix methods without overtesting them? I wouldn't like to repeat its logic when testing classes X and Y.

I don't think that turning RedisHelper's methods public is good, because they would be public in X and Y also.

Upvotes: 2

Views: 127

Answers (2)

Kache
Kache

Reputation: 16737

Direct answer

Test the module directly:

def test_redis_key_namespacing
  dummy_class = Class.new do # this anonymous class will not pollute namespace
    include RedisHelper

    public :key_for

    def self.name
      'Dummy'
    end
  end

  assert_equal 'Dummy-hello', dummy_class.new.key_for('hello')
end

Overall Suggestion

I recommend not using a RedisHelper module. It's good that you're trying to keep "Redis namespacing code" logic together and separated from other code, but what you really have is "Redis namespacing code" all over the place.

key_for is in both X and Y -- they have to handle the namespacing themselves every call to redis. Also, both key_for & prefix can be called in any of those classes (and subclasses and any other included modules) even though they're both internal to "Redis namespacing".

It looks like "Redis key namespacing" is an important concept in your application. It's reused in multiple places by different unrelated areas. Make it "a thing":

class NsRedis
  key_methods = Redis.instance_methods(false).select do |m|
    Redis.instance_method(m).parameters.first == [:req, :key]
  end

  # some metaprogramming, but it's static, much better than method_missing!
  key_methods.each do |m_name|
    define_method(m_name) do |key, *args|
      Redis.current.public_send("#{@namespace}:#{key}", *args)
    end
  end

  def initialize(namespace)
    @namespace = namespace
  end
end

# Now, anywhere you're going to need it, including tests:
class X
  def initialize
    @redis = NsRedis.new(self.class.name)
  end

  def do_something
    @redis.set(:my_key, 'my_val') # "unaware" of the namespacing at usage
  end
end

Upvotes: 2

Sergio Tulentsev
Sergio Tulentsev

Reputation: 230521

In RSpec you can use shared examples:

RSpec.shared_examples "Redis namespacing helper" do
  # your tests for key_for, prefix and what-have-you
end

RSpec.describe X do
  it_behaves_like "Redis namespacing helper"
end

RSpec.describe Y do
  it_behaves_like "Redis namespacing helper"
end

In minitest this is probably handled by simply including the module with shared tests.

Upvotes: 0

Related Questions