Search code examples
ruby-on-railsrubyrspec-rails

How to stub parent and child class in rspec


I have a class structure in a Rails (v7) app that looks like this:

# app/services/email_notifier.rb
class EmailNotifier
  def notify(template_data)
    # do things with email client
  end
end
# app/services/email_notifier/template_data.rb
class EmailNotifier
  class TemplateData
    def fill_in
      # do things with template and email client
    end
  end
end
# app/services/notification_service.rb
class NotificationService
  def call
    template_content = EmailNotifier::TemplateData.new(name: 'John Doe')
    EmailNotifier.notify(template_content)
  end
end

And what I'm trying to do is verify that the class methods are called:

# spec/services/notification_service.rb
require 'rails_helper'

RSpec.describe NotificationService do
  subject(:service) { described_class.new }
  let(:email_notifier) { class_double(EmailNotifier).as_stubbed_const }
  let(:email_template_class) { class_double(EmailNotifier::TemplateData).as_stubbed_const }
  let(:email_template) { instance_double(EmailNotifier::TemplateData) }

  before do
    allow(email_notifier).to receive(:notify)
    allow(email_template_class).to receive(:new).and_return(:email_template)
    allow(email_template).to receive(:fill_in)
  end

  it 'fills the template in' do
    service.call

    expect(email_template).to have_received(:fill_in)
  end
end

When I run rspec, I get this error:

     NameError:
       uninitialized constant #<ClassDouble(EmailNotifier) (anonymous)>::TemplateData

                 template_content = EmailNotifier::TemplateData.new

Now, I understand that I could be facing a problem from different origins:

  1. the structure I use is not being correctly interpreted by class loader when running the spec
  2. the "parent" class is stubbed and it cannot get its "child" class

As I suspect that what is happening is 2., the question is how can I make this kind of stub happen? Is this possible? Am I facing it in a wrong way?

I'm not sure stub_const works either as I expect in this spec.


Solution

  • Why mock the classes when you're stubbing the methods?

        allow(EmailNotifier).to receive(:notify)
        allow(EmailNotifier::TemplateData).to receive(:fill_in)