rmcsharry
rmcsharry

Reputation: 5552

RSpec testing a request that depends on a module method that returns a random number

I have the following module in a Rails api:

module SpeedCalculator

  def self.adjustment
    Random.rand(0..0.3)
  end

  def self.calculate_adjustment(track, car_speed)
    case track.surface_type
      when "asphalt"
        car_speed - (car_speed * self.adjustment).ceil
      when "gravel"
        car_speed - (car_speed * self.adjustment).ceil
      when "snow"
        car_speed - (car_speed * self.adjustment).ceil
      else
        car_speed
    end
  end  
end

I can successfully test that the adjustment method works like this:

require 'rails_helper'

RSpec.describe SpeedCalculator do
  include SpeedCalculator

  it "uses an adjustment value between 0 and 0.3" do
    expect(SpeedCalculator.adjustment).to be >= 0
    expect(SpeedCalculator.adjustment).to be <= 0.3
  end
end

It is possible to make an API request like this:

localhost:3000/api/v1/cars/porsche-911?track=monaco

where you are asking the system to calculate the speed for the given car on the given track.

So I need to write a request spec that for a given car and track, the correct value is returned. But how can I do that when the calculate_adjustment always applies a random number?

I believe that I need to create a mock/stub for self.adjustment, so the test would be something like this:

   it "calculates max_speed_on_track when a valid track name is provided" do
      Car.create!(name:'Subaru Impreza', max_speed:'120', speed_unit:'km/h')
      Track.create(name:'Monaco')

      # here create a dummy version of adjustment 
      # that always returns a fixed value, rather than a random value
      # and somehow use that dummy for the request?
      # Since x below needs to be a known value for the test to work.

      get api_v1_car_path(id: 'porsche-911', track: 'monaco')      
      expect(response).to have_http_status(200) 
      expect(response.body).to include_json(car: {max_speed_on_track: x})      
    end

Upvotes: 0

Views: 651

Answers (1)

Sergio Tulentsev
Sergio Tulentsev

Reputation: 230346

how can I do that when the calculate_adjustment always applies a random number?

I believe that I need to create a mock/stub for self.adjustment

Exactly! The last thing you want in tests is random behaviour (and random failures). You want reproducible results. So yeah, mock your RNG.

The simplest thing to do would be this:

expect(Random).to receive(:rand).with(0..0.3).and_return(0.1234)
# or better
expect(SpeedCalculator).to receive(:adjustment).and_return(0.1234)

# then proceed with your test
get api_v1_car_path(id: 'porsche-911', track: 'monaco') 
...

A further improvement here is to not use controller specs to test business logic. Encapsulate your business logic in an object and test that object (where you can use Dependency Injection technique to full extent). And your controller would simply become something like this:

class CarsController 
  def show
    calculator = MaxSpeedCalculator.new(params[:id], params[:track])
    render json: calculator.call
  end
end

Upvotes: 2

Related Questions