Как утверждать, что вызов метода не был сделан, без any_instance? - PullRequest
0 голосов
/ 19 октября 2018

У меня есть класс, который в одной ситуации должен вызывать :my_method, но в другой ситуации не должен вызывать метод :my_method.Я хотел бы проверить оба случая.Кроме того, я хотел бы, чтобы тест документировал случаи, когда :my_method не следует вызывать.

Использование any_instance обычно не рекомендуется , поэтому я был бы рад узнать хороший способчтобы заменить его.

Этот фрагмент кода является сокращенным примером того, что я хотел бы написать.

class TestSubject
  def call
    call_me
  end

   def call_me; end
   def never_mind; end
end

require 'rspec'

spec = RSpec.describe 'TestSubject' do
  describe '#call' do
    it 'calls #call_me' do
      expect_any_instance_of(TestSubject).to receive(:call_me)
      TestSubject.new.call
    end

    it 'does not call #never_mind' do
      expect_any_instance_of(TestSubject).not_to receive(:never_mind)
      TestSubject.new.call
    end
  end
end

spec.run # => true

Он работает, но использует метод expect_any_instance_of, которыйне рекомендуется.

Как его заменить?

Ответы [ 3 ]

0 голосов
/ 19 октября 2018

Вот так я обычно юнит-тестирую.Я обновил код для поддержки других возможных вопросов, которые у вас (или других читателей) могут возникнуть в будущем.

class TestSubject
  def call
    some_call_me_value = call_me
    call_you(some_call_me_value)
  end

  def call_me; end
  def call_you(x); end
  def never_mind; end

  class << self
    def some_class_method_a; end

    def some_class_method_b(x, y); end
  end
end

require 'rspec'

spec = RSpec.describe TestSubject do
  context 'instance methods' do
    let(:test_subject) { TestSubject.new }

    describe '#call' do
      let(:args) { nil }
      let(:mocked_call_me_return_value) { 'somecallmevalue' }
      subject { test_subject.call(*args) }

      before do
        allow(test_subject).to receive(:call_me) do
          mocked_call_me_return_value
        end
      end

      it 'calls #call_me' do
        expect(test_subject).to receive(:call_me).once
        subject
      end

      it 'calls #call_you with call_me value as the argument' do
        expect(test_subject).to receive(:call_you).once.with(mocked_call_me_return_value)
        subject
      end

      it 'does not call #never_mind' do
        expect(test_subject).to_not receive(:never_mind)
        subject
      end

      it 'calls in order' do
        expect(test_subject).to receive(:call_me).once.ordered
        expect(test_subject).to receive(:call_you).once.ordered
        subject
      end
    end

    describe '#call_me' do
      let(:args) { nil }
      subject { test_subject.call_me(*args) }

      # it ...
    end

    describe '#call_you' do
      let(:args) { nil }
      subject { test_subject.call_you(*args) }

      shared_examples_for 'shared #call_you behaviours' do
        it 'calls your phone number'
        it 'creates a Conversation record'
      end

      # just an example of argument-dependent behaviour spec
      context 'when argument is true' do 
        let(:args) { [true] }

        it 'does something magical'
        it_behaves_like 'shared #call_you behaviours'
      end

      # just an example of argument-dependent behaviour spec
      context 'when argument is false' do
        let(:args) { [false] }

        it 'does something explosive'
        it_behaves_like 'shared #call_you behaviours'
      end
    end
  end

  context 'class methods' do
    let(:args) { nil }

    describe '#some_class_method_a' do
      let(:args) { nil }
      subject { TestSubject.some_class_method_a(*args) }

      # it ...
    end

    describe '#some_class_method_b' do
      let(:args) { [1, 2] }
      subject { TestSubject.some_class_method_b(*args) }

      # it ...
    end
  end
end

spec.run # => true
0 голосов
/ 21 октября 2018

Не проверяйте, был ли вызван какой-либо метод или нет.
Это будет привязывать ваши тесты к деталям реализации и заставлять вас менять тесты каждый раз, когда вы будете выполнять рефакторинг (изменение деталей реализации без изменения поведения) вашего класса.проверяется.

Вместо проверки возвращаемого значения или измененного состояния приложения.
Сложно придумать пример, вы не предоставили достаточно контекста о тестируемом классе.

class CreateEntity
  def initialize(name)
    @name = name
  end 

  def call
    if company_name?(@name)
      create_company
    else
      create_person
    end
  end

  def create_person
    Person.create!(:name => @name)
  end

  def create_company
    Company.create!(:name => @name)
  end
end

# tests

RSpec.describe CreateEntity do
  let(:create) { CreateEntity.new(name).call }

  describe '#call' do
    context 'when person name is given' do
      let(:name) { 'Firstname Lastname' }
      it 'creates a person' do
        expect { create }.to change { Person.count }.by(1) 
      end

      it 'do not create a company' do
        expect { create }.not_to change { Company.count }
      end
    end

    context 'when company name is given' do
      let(:name) { 'Name & Sons Ltd' }
      it 'creates a company' do
        expect { create }.to change { Company.count }.by(1) 
      end

      it 'do not create a person' do
        expect { create }.not_to change { Person.count }
      end
    end
  end
end

С помощью тестов, приведенных выше, я смогу изменить способ реализации CreateEntity.call без изменения тестов, если поведение остается прежним.

0 голосов
/ 19 октября 2018

Я сделаю что-нибудь подобное

describe TestSubject do
  describe '#call' do
    it 'does not call #something' do 
      subject = TestSubject.new
      allow(subject).to receive(:something)

      subject.call

      expect(subject).not_to have_received(:something)
    end
  end
end

Надеюсь, это помогло!

...