简体   繁体   English

Rails Rspec测试中的多个期望

[英]Multiple Expectations in Rails Rspec test

I'm trying to test a ruby controller method, and I'm expecting multiple things to change in the database. 我正在尝试测试一个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

Gives the error 给出错误

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

What am I missing, or am I barking up the completely wrong tree? 我错过了什么,还是我咆哮着完全错误的树?

Version info: Rails 1.9.3 Rspec 2.11.0 版本信息:Rails 1.9.3 Rspec 2.11.0

Update: Based on Answer 更新:基于答案

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

But this runs the post action three times. 但是,这次运行三次。 It there any way to do this, but only execute the POST once? 有没有办法做到这一点,但只执行一次POST?

You probably want this type of test as an integration or request spec. 您可能希望将此类测试作为集成或请求规范。 Controllers should do more about asserting messages passed to models and setup of the view / return state. 控制器应该更多地关于断言传递给模型的消息和设置视图/返回状态。 Though, to answer the question, the response object doesn't have a call method on it. 但是,为了回答这个问题, response对象上没有call方法。 It is the returned state - things have already changed. 它是返回的状态 - 事情已经发生了变化。 You want to assert that the post does the changing. 你想断言post做了改变。

The expect change matcher needs to be attached to the block form of expect: expect change匹配器需要附加到期望的块形式:

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

Your current subject is not an expectation block, but the return value of the post action. 您当前的主题不是期望块,而是post操作的返回值。 So the implicit subject will not work for change . 因此隐含的主题不适用于change Also, as I stated above, you cannot use response.should for that same reason. 另外,正如我上面所说,出于同样的原因,你不能使用response.should You need to use the block form. 您需要使用块表单。

For this type of test, you will not be able to use the implicit subject, due to how RSpec handles expect{}. 对于这种类型的测试,由于RSpec处理expect{}. ,您将无法使用隐式主题expect{}. versus should . should

Also, arguably, to make the subject an expectation block would change the responsibilities and roles. 此外,可以说,使主题成为预期阻碍会改变责任和角色。 In general the object under test is that, an object. 通常,被测对象是一个对象。 By setting it to an action you're changing this and will likely confuse others down the road. 通过将其设置为动作,您正在改变这一点,并可能会让其他人感到困惑。 This may seem odd in this case, but that is due to Rails and how you interact with controller objects. 在这种情况下,这可能看起来很奇怪,但这是由于Rails以及您如何与控制器对象进行交互。

Update Based on Question Change: 根据问题变更更新:

Yes, it will make three post requests. 是的,它会发出三个post请求。 I'm going to assume you only want to make one request because of "performance". 我假设你只想提出一个请求,因为“性能”。 That can be an issue but there are many things you can do to help mitigate performance instead of simply making one request. 这可能是一个问题,但您可以采取许多措施来帮助降低性能,而不是简单地发出一个请求。

One philosophy for speeding up tests is to stub Active Record in controller tests, that keeps things short and fast. 加速测试的一个理念是在控制器测试中存根Active Record,这样可以保持简短和快速。 Integration specs are then used for the type of testing you are talking about (read http://www.andylindeman.com/2012/11/11/rspec-rails-and-capybara-2.0-what-you-need-to-know.html ). 然后将集成规范用于您正在讨论的测试类型(请阅读http://www.andylindeman.com/2012/11/11/rspec-rails-and-capybara-2.0-what-you-need-to- know.html )。 Yes, integration tests are slower normally; 是的,集成测试通常较慢; that is the price of doing a full stack test. 这是进行全栈测试的代价。

RSpec has a philosophy of 'test one thing'. RSpec的理念是“测试一件事”。 You want to re-execute the object / method under test each time. 您希望每次都重新执行测试中的对象/方法。 This ensures that there is no cross test contamination which can lead to faulty results. 这确保了没有交叉测试污染,这可能导致错误的结果。 As it is written, I would say you are already violating this be testing a database update and testing a text message expectation. 正如它所写,我会说你已经违反了这个测试数据库更新并测试文本消息的期望。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM