5

我正在尝试测试一个 ruby​​ 控制器方法,并且我期望数据库中的多个内容会发生变化。

context "With an unknown user" do
  let(:unknown_phone_number) { "0000000000" }
  subject {post :create, twiml_message(unknown_phone_number, "YES", "To" => twilio_phone_number) }

  it { response.should change(Card, :count).by(1) }
  it { should change(User, :count).by(1) }
  it { should change(Customer, :count).by(1) }```
end

给出错误

NoMethodError: undefined method `call' for #<ActionController::TestResponse:0x007fbd5d643030> ./spec/controllers/api/v1/sms_controller_spec.rb:25:in `block (4 levels) in <top (required)>'```

我错过了什么,或者我在吠叫完全错误的树?

版本信息:Rails 1.9.3 Rspec 2.11.0

更新:基于答案

it "registers an unknown user" do
    unknown_phone_number = "0000000000"
    expect {
      post :create, twiml_message(unknown_phone_number, "YES", "To" => twilio_phone_number)
    }.to change(Card, :count).by(1)

    open_last_text_message_for unknown_phone_number
    current_text_message.should have_body "Welcome to OnTab. You are now registered with a test account. To get started text TAB to open a tab."
  end

  it "registers an unknown user" do
    unknown_phone_number = "0000000000"
    expect {
      post :create, twiml_message(unknown_phone_number, "YES", "To" => twilio_phone_number)
    }.to change(Customer, :count).by(1)

    open_last_text_message_for unknown_phone_number
    current_text_message.should have_body "Welcome to OnTab. You are now registered with a test account. To get started text TAB to open a tab."
  end

  it "registers an unknown user" do
    unknown_phone_number = "0000000000"
    expect {
      post :create, twiml_message(unknown_phone_number, "YES", "To" => twilio_phone_number)
    }.to change(User, :count).by(1)

    open_last_text_message_for unknown_phone_number
    current_text_message.should have_body "Welcome to OnTab. You are now registered with a test account. To get started text TAB to open a tab."
  end

但这会运行 3 次 post 操作。有什么办法可以做到这一点,但只执行一次POST?

4

1 回答 1

2

您可能希望将这种类型的测试作为集成或请求规范。控制器应该做更多关于断言传递给模型的消息和视图/返回状态的设置。但是,要回答这个问题,该response对象上没有call方法。这是返回的状态 - 事情已经发生了变化。您想断言post会发生变化。

expect change匹配器需要附加到期望的块形式:

it do
  expect{ post :create, ... }.to change(User, :count).by(1)
end

您当前的主题不是期望块,而是post操作的返回值。所以隐式主语不适用于change. 此外,正如我上面所说,您不能response.should出于同样的原因使用。您需要使用块形式。

对于这种类型的测试,由于 RSpec 处理expect{}.should.

此外,可以说,使主题成为期望块会改变职责和角色。一般来说,被测对象是一个对象。通过将其设置为一个动作,您正在改变这一点,并且可能会混淆其他人。在这种情况下,这可能看起来很奇怪,但这是由于 Rails 以及您与控制器对象的交互方式。

基于问题变化的更新:

是的,它将提出三个post请求。我将假设您因为“性能”而只想提出一个请求。这可能是一个问题,但您可以做很多事情来帮助降低性能,而不是简单地提出一个请求。

加速测试的一种理念是在控制器测试中存根 Active Record,这样可以使事情变得简短而快速。然后将集成规范用于您正在谈论的测试类型(阅读http://www.andylindeman.com/2012/11/11/rspec-rails-and-capybara-2.0-what-you-need-to-知道.html )。是的,集成测试通常较慢;这就是进行全栈测试的代价。

RSpec 的理念是“测试一件事”。您希望每次都重新执行被测对象/方法。这确保了没有可能导致错误结果的交叉测试污染。正如它所写的那样,我想说您已经违反了测试数据库更新和测试文本消息期望的规定。

context 'registering a new user' do
  it 'saves the user in the database' do
    unknown_phone_number = '000'
    new_card = mock_model(Card)
    expect(Card).to receive(:new).and_return(new_card)

    expect(new_card).to receive(:save).and_return(true)

    post :create #...
  end

  it 'sends a welcome message to the phone' do
    # You can stub the text class here and assert on messages passed
    # As you probably trust that if the message is passed the text message code
    # does what it was designed to do (you have unit tests for it right?)
  end
end
于 2013-06-26T16:33:28.620 回答