如何断言没有any_instance没有进行方法调用?

时间:2018-10-19 09:07:02

标签: ruby-on-rails testing rspec tdd

我有一个类,在一种情况下应调用:my_method,但在另一种情况下则不得调用方法:my_method。我想测试两种情况。另外,我希望测试记录不应该调用:my_method的情况。

Using any_instance is generally discouraged,所以我很乐于学习替换它的好方法。

此代码段是我想编写的那种测试的简化示例。

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 :(得分:3)

我会那样做

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

希望这对您有帮助!

答案 1 :(得分:1)

这是我通常进行单元测试的方式。我更新了代码以支持您(或其他读者)将来可能遇到的其他可能的问题。

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

答案 2 :(得分:0)

不要测试是否调用了某种方法。
这会将您的测试限制在实现细节上,并且每次您重构被测类时都将迫使您更改测试(更改实现细节而不更改行为)。

代替测试返回值或更改的应用程序状态。
很难拿出示例,因为您没有提供有关所测试类的足够上下文。

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方法的实现方式,而无需改变测试,只要行为保持不变即可。