RSpec 测试依赖于 returns 随机数的模块方法的请求

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

我在 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

我可以成功测试调整方法是这样的:

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

可以像这样发出 API 请求:

localhost:3000/api/v1/cars/porsche-911?track=摩纳哥

您要求系统计算给定赛道上给定汽车的速度的位置。

所以我需要编写一个请求规范,以针对给定的汽车和赛道返回正确的值。但是,当 calculate_adjustment 总是应用随机数时,我该怎么做呢?

我认为我需要为 self.adjustment 创建一个 mock/stub,所以测试应该是这样的:

   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

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

正是!您在测试中最不想要的是随机行为(和随机失败)。您想要可重现的结果。所以,是的,嘲笑你的 RNG。

最简单的做法是:

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') 
...

这里的进一步改进是不使用控制器规范来测试业务逻辑。将您的业务逻辑封装在一个对象中并测试该对象(您可以在其中充分使用依赖注入技术)。你的控制器会变成这样:

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