lundi 5 juin 2017

Ruby on Rails, Rspec: Can someone please help me in converting database calls with mocks/doubles?

So I am working on a reminder scheduler and used rails scaffold to create the models and views for the reminder. So far I have the controller looks like this:

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

The spec looks like this:

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

I am trying to replace all the create statements with a mock/double to eliminate database calls. I have tried replacing all create statements with this:

reminder = double('Reminder', id: 1, reminder_day: :monday, reminder_time: Time.now)

and other variations. But, it does not seem to work. I have been struggling with this for sometime now. I'd appreciate if someone could help me with this.

Aucun commentaire:

Enregistrer un commentaire