Проверка моков rspec перед окончанием теста

Похоже, что стандартный способ использования макетов rspec в тестовом примере — это сделать что-то вроде этого:

class MyTest
  def setup
    super
    ::RSpec::Mocks.setup(self)
  end

  def teardown
    super
    begin
      ::RSpec::Mocks.verify
    ensure
      ::RSpec::Mocks.teardown
    end
  end

  test "something"
    foo = MyFoo.new
    expect(foo).to receive(:bar).and_return(42)
    ret = SomeClass.call_bar(foo)

    assert_equal(42, ret)
  end
end

Работает хорошо. Но если SomeClass.call_bar использовала возврат foo.bar в качестве возврата, и что-то было не так с кодом, так что foo.bar никогда не вызывался, то я получаю отказ только из-за строки assert_equal(42, ret). Я не вижу никакой ошибки, например:

RSpec::Mocks::MockExpectationError: (foo).bar
    expected: 1 time
    received: 0 times

Если я удалю строку assert_equal(42, ret), то получу ошибку ожидания rspec. Но я хочу проверить обе вещи, что foo.bar был вызван и окончательный возврат был 42. Более важно знать, что foo.bar не вызывался, так как это источник причины, по которой 42 не было возвращено .

Если я ожидаю что-то вроде: expect(foo).not_to receive(:bar), то я получаю эту ошибку ожидания прямо в источнике вызова, а не позже во время разборки.

Теперь я могу сделать что-то вроде ::RSpec::Mocks.verify непосредственно перед вызовом assert_equal, но это кажется неправильным. Я также не уверен, должен ли я убирать макеты в этот момент или нет.

Есть ли какой-то синтаксис, например:

  test "something"
    foo = MyFoo.new
    ret = nil

    expect(foo).to receive(:bar).and_return(42).during do
      ret = SomeClass.call_bar(foo)
    end

    assert_equal(42, ret)
  end

Чтобы проверка происходила сразу после того, как блок перешел на during? Или, может быть, если у вас есть несколько двойников, вы можете сделать что-то вроде:

    expect(dbl1).to receive(:one)
    expect(dbl2).to receive(:two)
    expect(dbl3).to receive(:three)

    verify(dbl1, dbl2, dbl3).during do
      my_code
    end

person onlynone    schedule 27.06.2019    source источник


Ответы (3)


Вы ищете шпионов rspec.

Шпионы — это альтернативный тип тестовых двойников, которые поддерживают этот шаблон, позволяя вам ожидать, что сообщение было получено постфактум, используя have_received.

Вы создаете частичный двойник из своего foo с allow(...).to receive, затем можете подтвердить прием сообщения:

test "something"
  foo = MyFoo.new
  allow(foo).to receive(:bar).and_return(42)
  ret = SomeClass.call_bar(foo)
  expect(foo).to have_received(:bar)
  assert_equal(42, ret)
end
person Chris Heald    schedule 27.06.2019
comment
Это не поможет, так как ошибочное утверждение expect(foo).to have_received(:bar) прервет тест. - person Grzegorz; 27.06.2019
comment
Я бы не согласился с этим; ОП указал, что знание того, что звонка не было, является более важным утверждением. Мой тест, как написано, если метод не вызывается, приведет к указанию, что объект не получил сообщение. Если возвращаемое значение зависит от получения сообщения, то неполучение сообщения всегда означает провал второго теста. Совокупные сбои в этом случае никак не помогают устранить причину сбоя и, возможно, запутывают воду. - person Chris Heald; 27.06.2019
comment
Ой, простите. Я не заметил изменения порядка утверждений. Можете ли вы немного отредактировать свой ответ, чтобы я мог удалить отрицательный голос? - person Grzegorz; 28.06.2019
comment
Это довольно близко, allow вызов до, а затем expect после... Я дам ему некоторое время, чтобы посмотреть, поступят ли какие-либо другие ответы, но этот, будучи наиболее близким, вероятно, будет принят. - person onlynone; 28.06.2019

Я считаю, что вам нужно объединить ошибки https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/aggregating-failures

В «нормальной» настройке любая ошибка прерывает тест, и более поздние утверждения не проверяются.

person Grzegorz    schedule 27.06.2019
comment
Это не отвечает на вопрос о том, как на самом деле проверить вызов сообщения. - person Chris Heald; 27.06.2019
comment
Это все еще проверяется на этапе демонтажа, это решение, которое помогает прервать тест при первом ошибочном утверждении. - person Grzegorz; 28.06.2019
comment
Это похоже на то, что мне нужно, но assert из minitest, а expect из rspec, поэтому я не думаю, что они будут объединены вместе. - person onlynone; 28.06.2019
comment
Вы пробовали? Агрегация означает, что тест не прерывается после первого сбоя, поэтому есть вероятность, что произойдет разрыв. - person Grzegorz; 28.06.2019
comment
Разве мне не нужно, чтобы это было функцией минитеста? Мне нужен минитест, чтобы продолжить демонтаж после сбоя assert_equal(42, ret). И он должен будет включать в отчет сбои/ошибки из-за разборки. - person onlynone; 28.06.2019

Я не думаю, что есть встроенный способ сделать это, но если вы добавите следующий класс:

class VerifyDuring
  def initialize(test, objects)
    @test = test
    @objects = objects
  end

  def during
    yield
  ensure
    begin
      @objects.each do |object|
        RSpec::Mocks.proxy_for(object).verify
      end
    rescue Exception => e
      @test.flunk e
    end
  end
end

И следующий метод для вашего тестового класса:

  def verify(*objects)
    VerifyDuring.new(self, objects)
  end

Ты можешь это сделать:

    verify(dbl1, dbl2, dbl3).during do
      my_code
    end
person onlynone    schedule 27.06.2019