Ruby on rails 如何在Rails和Rspec中使用mock/double转换数据库调用?

Ruby on rails 如何在Rails和Rspec中使用mock/double转换数据库调用?,ruby-on-rails,testing,rspec,tdd,rspec-rails,Ruby On Rails,Testing,Rspec,Tdd,Rspec Rails,我正在开发一个提醒调度程序,并使用rails scaffold为提醒创建模型和视图。到目前为止,控制器的外观如下所示: class RemindersController < ApplicationController before_action :set_reminder, only: [:show, :edit, :update, :destroy] @@reminder_hash = {} @@scheduler = Rufus::Scheduler.singleton

我正在开发一个提醒调度程序,并使用rails scaffold为提醒创建模型和视图。到目前为止,控制器的外观如下所示:

class RemindersController < ApplicationController
  before_action :set_reminder, only: [:show, :edit, :update, :destroy]

  @@reminder_hash = {}
  @@scheduler = Rufus::Scheduler.singleton

  def index
    @reminders = Reminder.all
  end

  def new
    @reminder = Reminder.new
  end

  def edit
  end

  def create
    @reminder = Reminder.new(reminder_params)

    respond_to do |format|
      if @reminder.save
        format.html { redirect_to reminders_url, notice: I18n.t('Reminder was successfully created.') }
        format.json { render :show, status: :created, location: @reminder }

        schedule_reminder @reminder.id, @reminder.reminder_day, @reminder.reminder_time
      else
        format.html { render :new }
        format.json { render json: @reminder.errors, status: :unprocessable_entity }
      end
    end
  end

  def update
    respond_to do |format|
      if @reminder.update(reminder_params)
        # Unschedule the job.
        jobs = @@scheduler.jobs(tag: @reminder.id)
        jobs.each { |job| @@scheduler.unschedule(job) }
        # Reschedule the job at the new time.
        schedule_reminder @reminder.id, @reminder.reminder_day, @reminder.reminder_time

        format.html { redirect_to reminders_url, notice: I18n.t('Reminder was successfully updated.') }
        format.json { render :show, status: :ok, location: @reminder }
      else
        format.html { render :edit }
        format.json { render json: @reminder.errors, status: :unprocessable_entity }
      end
    end
  end

  def destroy
    @reminder.destroy
    respond_to do |format|
      jobs = @@scheduler.jobs(tag: @reminder.id)
      jobs.each { |job| @@scheduler.unschedule(job) }
      format.html { redirect_to reminders_url, notice: I18n.t('Reminder was successfully destroyed.') }
      format.json { head :no_content }
    end
  end

  # Internal: Schedule reminder to be sent at appropriate time.
  #
  # id            - The Reminder id used to uniquely identify the job associated with that reminder.
  # reminder day  - The day when the reminder will be sent.
  # reminder time - The time when the reminder will be sent.
  #
  # Returns nothing.
  def schedule_reminder(tag, reminder_day, reminder_time)
    minute = reminder_time.strftime('%M')
    hour = reminder_time.strftime('%H')
    day = reminder_day[0...3].upcase
    cron_string = '0 ' + minute + ' ' + hour + ' * * ' + day

    @@scheduler.cron cron_string, tag: tag do
      EmailController.mentor_reminder_email_sender
    end
  end

  private

  # Use callbacks to share common setup or constraints between actions.
  def set_reminder
    @reminder = Reminder.find(params[:id])
  end

  # Whitelist the paramters.
  def reminder_params
    params.fetch(:reminder, {}).permit(:reminder_day, :reminder_time)
  end
end
require 'rails_helper'
RSpec.describe RemindersController, type: :controller do
  let(:valid_attributes) {
    { id: 1, reminder_day: :monday, reminder_time: Time.now}
  }

  let(:invalid_attributes) {
    { id: nil, reminder_day: nil, reminder_time: nil}
  }

  let(:valid_session) { {} }

  describe 'DELETE #destroy' do
    it 'destroys the requested reminder' do
      reminder = Reminder.create! valid_attributes
      expect {
        delete :destroy, {id: reminder.to_param}, valid_session
      }.to change(Reminder, :count).by(-1)
    end

    it 'does not call reminder scheduler function' do
      post :create, {reminder: valid_attributes}, valid_session
      expect(controller).to_not receive(:schedule_reminder)
    end

    it 'redirects to the reminders list' do
      reminder = Reminder.create! valid_attributes
      delete :destroy, {id: reminder.to_param}, valid_session
      expect(response).to redirect_to(reminders_path)
    end
  end

  describe '#create' do
    let(:reminder) { Reminder.new(id: 1, reminder_day: 1, reminder_time: Time.now) }
    let(:reminders_controller) { RemindersController.new }

    context 'with valid params' do
      before do
        allow(controller).to receive(:schedule_reminder)
      end

      it 'creates a new Reminder' do
        expect {
          post :create, {reminder: valid_attributes}, valid_session
        }.to change(Reminder, :count).by(1)
      end

      it 'assigns a newly created reminder as @reminder' do
        post :create, {reminder: valid_attributes}, valid_session
        expect(assigns(:reminder)).to be_a(Reminder)
        expect(assigns(:reminder)).to be_persisted
      end

      it 'calls schedule_reminder with correct parameters' do
        expect(reminders_controller).to receive(:schedule_reminder)
          .with(reminder.id, reminder.reminder_day, reminder.reminder_time)
        reminders_controller.schedule_reminder(reminder.id, reminder.reminder_day, reminder.reminder_time)
      end
    end

    context 'with invalid params' do
      it 'assigns a newly created but unsaved reminder as @reminder' do
        post :create, {reminder: invalid_attributes}, valid_session
        expect(assigns(:reminder)).to be_a_new(Reminder)
      end

      it 're-renders the "new" template' do
        post :create, {reminder: invalid_attributes}, valid_session
        expect(response).to render_template('new')
      end

      it 'does not call schedule_reminder' do
        expect(controller).to_not receive(:schedule_reminder)
      end
    end
  end

  describe 'PUT #update' do
    context 'with valid params' do
      let(:new_attributes) {
        { id: 2, reminder_day: :tuesday, reminder_time: Time.now}
      }
      let(:reminders_controller) { RemindersController.new }
      let(:reminder) { Reminder.new(id: 1, reminder_day: 1, reminder_time: Time.now) }

      it 'updates the requested reminder' do
        reminder = Reminder.create! valid_attributes
        put :update, { id: reminder.to_param, reminder: new_attributes}, valid_session
        reminder.reload
      end

      it 'calls reminder scheduler function with correct parameters' do
        post :create, {reminder: valid_attributes}, valid_session
        expect(reminders_controller).to receive(:schedule_reminder)
          .with(reminder.id, reminder.reminder_day, reminder.reminder_time)
        reminders_controller.schedule_reminder(reminder.id, reminder.reminder_day, reminder.reminder_time)
      end

      it 'assigns the requested reminder as @reminder' do
        reminder = Reminder.create! valid_attributes
        put :update, { id: reminder.to_param, reminder: valid_attributes}, valid_session
        expect(assigns(:reminder)).to eq(reminder)
      end

      it 'redirects to the reminder' do
        reminder = Reminder.create! valid_attributes
        put :update, { id: reminder.to_param, reminder: valid_attributes}, valid_session
        expect(response).to redirect_to(reminders_path)
      end
    end

    context 'with invalid params' do
      it 'assigns the reminder as @reminder' do
        reminder = Reminder.create! valid_attributes
        put :update, {id: reminder.to_param, reminder: invalid_attributes}, valid_session
        expect(assigns(:reminder)).to eq(reminder)
      end

      it 're-renders the "edit" template' do
        reminder = Reminder.create! valid_attributes
        put :update, {id: reminder.to_param, reminder: invalid_attributes}, valid_session
        expect(response).to render_template('edit')
      end

      it 'does not call schedule_reminder' do
        expect(controller).to_not receive(:schedule_reminder)
      end
    end
  end

  describe 'GET #index' do
    it 'assigns all reminders as @reminders' do
      reminder = Reminder.create! valid_attributes
      get :index, {}, valid_session
      expect(assigns(:reminders)).to eq([reminder])
    end
  end

  describe 'GET #new' do
    it 'assigns a new reminder as @reminder' do
      get :new, {}, valid_session
      expect(assigns(:reminder)).to be_a_new(Reminder)
    end
  end

  describe 'GET #edit' do
    context 'when reminder is found' do
      it 'assigns the requested reminder as @reminder' do
        reminder = Reminder.create! valid_attributes
        get :edit, {id: reminder.to_param}, valid_session
        expect(assigns(:reminder)).to eq(reminder)
      end
    end

    context 'when reminder is not found' do
      it 're-renders the "edit" template' do
        reminder = Reminder.create! valid_attributes
        put :update, {id: reminder.to_param, reminder: invalid_attributes}, valid_session
        expect(response).to render_template('edit')
      end
    end
  end
end
我试图用mock/double替换所有create语句,以消除数据库调用。我已尝试用以下内容替换所有create语句:

reminder = double('Reminder', id: 1, reminder_day: :monday, reminder_time: Time.now)
describe 'DELETE #destroy' do
    let(:reminder){double('Reminder', id: 1, reminder_day: :monday, reminder_time: Time.now)}
    before do
      allow(Reminder).to receive(:create).and_return(reminder)
    end
    it 'destroys the requested reminder' do
      reminder = Reminder.create
      expect {
        delete :destroy, {id: reminder.to_param}, valid_session
      }.to change(Reminder, :count).by(-1)
    end

    it 'does not call reminder scheduler function' do
      post :create, {reminder: valid_attributes}, valid_session
      expect(controller).to_not receive(:schedule_reminder)
    end

    it 'redirects to the reminders list' do
      reminder = Reminder.create
      delete :destroy, {id: reminder.to_param}, valid_session
      expect(response).to redirect_to(reminders_path)
    end
  end
和其他变体。 我替换了这个:

describe 'DELETE #destroy' do
    it 'destroys the requested reminder' do
      reminder = Reminder.create! valid_attributes
      expect {
        delete :destroy, {id: reminder.to_param}, valid_session
      }.to change(Reminder, :count).by(-1)
    end

    it 'does not call reminder scheduler function' do
      post :create, {reminder: valid_attributes}, valid_session
      expect(controller).to_not receive(:schedule_reminder)
    end

    it 'redirects to the reminders list' do
      reminder = Reminder.create! valid_attributes
      delete :destroy, {id: reminder.to_param}, valid_session
      expect(response).to redirect_to(reminders_path)
    end
  end
为此:

reminder = double('Reminder', id: 1, reminder_day: :monday, reminder_time: Time.now)
describe 'DELETE #destroy' do
    let(:reminder){double('Reminder', id: 1, reminder_day: :monday, reminder_time: Time.now)}
    before do
      allow(Reminder).to receive(:create).and_return(reminder)
    end
    it 'destroys the requested reminder' do
      reminder = Reminder.create
      expect {
        delete :destroy, {id: reminder.to_param}, valid_session
      }.to change(Reminder, :count).by(-1)
    end

    it 'does not call reminder scheduler function' do
      post :create, {reminder: valid_attributes}, valid_session
      expect(controller).to_not receive(:schedule_reminder)
    end

    it 'redirects to the reminders list' do
      reminder = Reminder.create
      delete :destroy, {id: reminder.to_param}, valid_session
      expect(response).to redirect_to(reminders_path)
    end
  end
我发现以下错误:
失败/错误:预期(响应)。将\u重定向到(提醒\u路径) 预期响应为a,但为

失败/错误:预期{
预计#计数已更改为-1,但更改为0

值得一读:.“似乎不起作用”也不是工作问题描述。看起来第一个测试没有查询正确的路径。在对第二个错误使用块/更改设置之前,您可能希望在块之外测试销毁方法。一个好的计划是,在移动到下一个之前,一次一步通过控制器测试一行测试。