0
\$\begingroup\$

I am trying to test #initialize of class Author:

# lib/author.rb
class Author
  attr_reader :name, :filename, :website

  def initialize(name, filename, website)
    @name = name
    @filename = filename
    set_filename_from_name if filename == :auto
    @website = website
  end
end

In reality there are 8-9 properties of Author, not just 3 and I've decided to require them all in #initialize.

I am trying to dry up my specs, but I have lots of repetitions.

I have created a helper module:

#spec/helpers/create_author.rb
module CreateAuthor
  # Creates author with default values except for those listed
  # under params.
  def create_author(params = {})
    defaults = {
      name: "Ford",
      filename: "Ford",
      website: 'www.ford.com'
    }
    opt = defaults.merge(params)

    Author.new(opt[:name],
      opt[:filename],
      opt[:website])
  end
end

But now I have untested code which I need to test. This is spec for that helper method:

# spec/authors_helper_spec.rb
require_relative 'helpers/create_author'

describe 'Create Author Helper' do
  # subject do
  #   Class.new { include CreateAuthor }
  # end
  include CreateAuthor

  let(:defaults) do
    {
      name: "Ford",
      filename: "Ford",
      website: 'www.ford.com'
    }
  end


  describe '#create_author' do
    it 'returns author with default values when called with no parameters' do
      author = create_author()

      expect(author.class).to eq(Author)
      expect(author.name).to eq(defaults[:name])
      expect(author.filename).to eq(defaults[:filename])
      expect(author.website).to eq(defaults[:website])
    end

    it 'returns author with custom name when specified' do
      author = create_author(name: 'New Name')

      expect(author.class).to eq(Author)
      expect(author.name).to eq('New Name')
      expect(author.filename).to eq(defaults[:filename])
      expect(author.website).to eq(defaults[:website])          
    end
  end
end

I will need to do similar thing for other class, so I will just change create_author(options={}) to create_class(class, options={}) and test that.

How can I DRY this up and make it less verbose?

\$\endgroup\$
3
  • \$\begingroup\$ Is it possible to use keyword arguments for your contructor? \$\endgroup\$
    – slowjack2k
    Commented Oct 11, 2016 at 11:45
  • \$\begingroup\$ That would make testing easier, but calling it more difficult. There are so many params I can't even remember what they are. So I just copy paste parameter list of initialize method and then handle the code before it to initialize all params. I have purposely put it in constructor so that I do not forget to initalize some of them, as there are so many. \$\endgroup\$ Commented Oct 11, 2016 at 12:20
  • \$\begingroup\$ do you really need to test your test helpers? \$\endgroup\$ Commented Oct 23, 2016 at 20:03

3 Answers 3

1
\$\begingroup\$

You could use the following solution unless it's important to call initialize in "create_author".

# spec/helpers/create_author.rb
module CreateEntity
  def create_entity(klass, params = {}, defaults = {})
    opt = defaults.merge(params)

    entity = klass.allocate
    opt.each do |key, value|
      entity.instance_variable_set("@#{key}", value)
    end
    entity
  end

  def create_author(params = {})
    create_entity(Author, params, {
      name: "Ford",
      filename: "Ford",
      website: 'www.ford.com'
    })
  end
end

create_author(name: "Ford 1")
\$\endgroup\$
2
  • \$\begingroup\$ Thank you. Calling initialize is important because validation logic is in there. \$\endgroup\$ Commented Oct 12, 2016 at 14:36
  • \$\begingroup\$ You have presented an alternative solution, but haven't reviewed the code. Please explain your reasoning (how your solution works and why it is better than the original) so that the author and other readers can learn from your thought process. \$\endgroup\$ Commented Oct 12, 2016 at 14:40
0
\$\begingroup\$

I would give shared examples a try. It's not tested, but it should work like this:

#spec/helpers/shares_examples/check_default_behaviour.rb

RSpec.shared_examples 'check default behaviour' do |class_to_instantiate|

  def create_instance(params = {})
    opt = defaults.merge(params)

    class_to_instantiate.new(opt[:name],
               opt[:filename],
               opt[:website])
  end

  # let(:defaults) has to be set within your spec

  describe "#create_#{class_to_instantiate}" do
    it "returns #{class_to_instantiate} with default values when called with no parameters" do
      instance = create_instance()

      expect(instance.class).to eq(class_to_instantiate)
      expect(instance.name).to eq(defaults[:name])
      expect(instance.filename).to eq(defaults[:filename])
      expect(instance.website).to eq(defaults[:website])
    end

    it "returns #{class_to_instantiate} with custom name when specified" do
      instance = create_instance(name: 'New Name')

      expect(instance.class).to eq(class_to_instantiate)
      expect(instance.name).to eq('New Name')
      expect(instance.filename).to eq(defaults[:filename])
      expect(instance.website).to eq(defaults[:website])
    end
  end
end

# spec/...
describe 'Create Author Helper' do
  let(:defaults) do
    {
        name: "Ford",
        filename: "Ford",
        website: 'www.ford.com'
    }
  end

  include_examples 'check default behaviour', Author
end
\$\endgroup\$
0
\$\begingroup\$

You should work on making your test cases rely on shared code as much as possible.

It's part of a larger strategy to make more 'testable' code. In your main application, if you have three similar methods and want 100% unit test coverage, you'd have to test all three. But if you refactor the app code to use one method instead, then both the app code and the tests will be more terse.

To reiterate - just for the sake of sanity, if you're setting out to write 100% unit test coverage it's worth trying to write 'testable' code - there is no magic bullet to make a concise test suite if your code is sprawling.

I may be misunderstanding, but are you trying to test your helper in addition to your application code? To do so might be a little much - are people really testing their test suites?

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.