Home > OS >  When testing a method call that I know happens , I get a failure error stating the method was called
When testing a method call that I know happens , I get a failure error stating the method was called

Time:10-02

I have a controller that calls the CSVCreator class when a get request is made. People is just a list of Persons that have some basic values like name and title.

def index
    respond_to do |format|
      format.html
      format.csv { send_data CSVCreator.new(people).create }
end

My CSVCreator looks like this

class CSVCreator 

  HEADERS = ["Name", "Date", "Title"].freeze

  def initialize(people)
      @people = people
  end

  def generate
      CSV.generate do |csv|
        csv << HEADERS
        @people.each do |person|
          row = [person.name, person.date, person.title] 
          csv << row
        end
      end
  end
end

I'm trying to figure out how I would go about testing this in Rspec? What I've tried is

it "calls CSVCreator when get request is made" do
   csv_creator = double("CSVCreator")
   people = double("People")
   
   allow(csv_creator).to receive(:new).with(people)
   allow(csv_creator).to receive(:create)
   
   expect(csv_creator).to receive(:new).with(people)
   expect(csv_creator).to receive(:create)

   get :index, format: :csv
end

My thought process was to decouple the controller and the CSVCreator and People classes. So to test the controller, I wanted to see if it correctly calls the methods it needs to, so I created test doubles for those objects. I'm new to RSpec and testing in general, so please let me know if my approach was incorrect.

My issue is, I get a failure saying

     Failure/Error: expect(csv_creator).to receive(:new).with(people)
 
   (Double "CSVCreator").new(#<Double "People">)
       expected: 1 time with arguments: (#<Double "People">)
       received: 0 times

I know my class works and it creates the CSV, and that CSVCreator.new(people).create being called in the controller. So I'm curious as to why I'm receiving this failure.

CodePudding user response:

Create a class method on your CSVCreator that creates an instance and calls the generate method:

class CSVCreator 
  # ...
  def self.perform(people)
    new(people).generate
  end
end
def index
  respond_to do |format|
    format.html
    format.csv { send_data CSVCreator.perform(people) }
  end
end

This creates a better API so that consumers don't have to have as much knowledge about the class and it makes it way easier to set expectations:

it "calls CSVCreator when get request is made" do
   expect(CSVCreator).to receive(:perform)
   get :index, format: :csv
end

However stubbing in people is a bit more problematic and depends on how its actually implemented. If its a method on the controller it would be hard to stub without using any_instance which is a code smell.

CodePudding user response:

i guess that the input param people is created somewhere in your controller, but i'm sure it's not your double "People", you have to stub the method :new of the People class to return an instance_double of "People", Or in case you want to make sure that CSVCreator is created with a special "People", for example in your controller you call this to get people: PeopleService.some_logic(..) -> a_special_people, then stub that method allow(PeopleService).to receive(:some_logic).and_return(instance_double_people)

beside that, you could also create an instance_double of CSVCreator instead, then stub method :new of CSVCreator to return that instance_double, now you only need to verify that instance_double has called :create method since it makes sure that CSVCreator has called :new and the new instance of CSVCreator has called :create.

it "calls CSVCreator when get request is made" do
   csv_creator = instance_double(CSVCreator)
   people = instance_double(People)

   allow(People).to receive(:new).and_return(people)
   # allow(PeopleService).to receive(:some_logic).and_return(people)
   allow(CSVCreator).to receive(:new).with(people).and_return(csv_creator)
   allow(csv_creator).to receive(:create)
   
   get :index, format: :csv
   
   expect(csv_creator).to have_received(:create)
end
  • Related