简体   繁体   English

Rails RSpec控制器测试

[英]Rails RSpec Controller Test

I'm trying to test that a Subscription was actually created as part of my Company#create controller method: 我正在尝试测试实际上是作为Company#create控制器方法的一部分创建的Subscription

require 'rails_helper'

RSpec.describe CompaniesController, :type => :controller do

  before do
    user = build(:user, :company => nil)
    login_with(user)
  end

  it "should have a subscription" do
    company = build(:company).attributes
    terms_accepted = { terms_accepted: true }
    @company_params = company.merge(terms_accepted)
    post :create, { company: @company_params }
    # I need to expect something here like company.subscription.exists?
  end
end

I've already created a test to verify a company is created which is contained in the 4 lines I currently have in the test above. 我已经创建了一个测试来验证创建的company ,该company包含在我上面的测试中当前包含的4行中。

I have a company factory that also creates a subscription : 我有一家company工厂,该工厂还创建了一个subscription

FactoryGirl.define do
  factory :company do
    name "ACME Test"
    address_1 "123 Shady Lane."
    address_2 "Suite 400"
    city "Testville"
    state "Somewhere"
    zip_code "12345"
    has_payment_plan false
    stripe_id "cus_34d434343e4e3e3"
    locked false
    association :subscription
  end
end

And... I have a subscription factory: 还有...我有一个subscription工厂:

FactoryGirl.define do
  factory :subscription do
    company_id 1
    trial true
    custom nil
    convert_date nil
    per_device_price 3
    trial_expires 5.days.from_now
    freemium false
  end
end

And here is my Company#create controller method... (yes, it has alot of bloat, which I'm eventually moving to the model once I have a base-line of tests that work) : 这是我的Company#create控制器方法... (是的,它有很多膨胀,一旦我有了一个可以正常工作的测试基准,我最终就会转向模型)

def create
    company = Company.new(company_params)
    user = current_user

    if company.save && params[:terms]  == "1"
      user.company = company
      accepted = true
      user.terms_accepted = accepted
      user.save
      Subscription.create(company_id: user.company.id, trial: true, charge_date: Date.today + 1.month, per_device_price: 5.00, trial_expires: Date.today + 5.days)
      device = Device.create(is_registered: true, registered_date: Date.today, company_id: user.company.id, name: user.company.name, is_sent: true, sent_date: Date.today)
      location = Location.create(company_id: user.company.id, identifier: device.identifier, name: company.name)
      device.serial_number = device.identifier
      NewDevice.new_device(user, device).deliver
      flash[:success] = "Welcome!"
      redirect_to root_path
    else
      flash[:notice] = "You did not accept the terms of service."
      sign_out(user)
      redirect_to root_path
    end  
end

I thought I was on the right track by just checking to see if Subscription was increased by 1 , as I did with my Company#create test, but that's returning: 我以为我只是通过检查Subscription was increased by 1来走上正轨,就像我对Company#create测试所做的那样,但是返回的是:

Failures:

  1) CompaniesController should have a subscription
     Failure/Error:
       expect {
         post :create, { company: @company_params }
       }.to change(Subscription, :count).by(1)

       expected #count to have changed by 1, but was changed by 0

Am I doing this incorrectly? 我做错了吗?

In your spec file companies_controller_spec.rb I would do something like this: 在您的规格文件companies_controller_spec.rb我将执行以下操作:

let(:valid_params) {
  attributes_for(:company).merge(terms_accepted: true)
}

context 'POST #create' do
  it 'creates a company' do
    expect do
      post :create, params: { company: valid_params }, session: {}
    end.to change(Company, :count).by(1)
  end

  it 'creates a subscription' do
    expect do
      post :create, params: { company: valid_params }, session: {}
    end.to change(Subscription, :count).by(1)
  end
end

If the second spec fails, you'll need to troubleshoot why. 如果第二个规范失败,则需要解决原因。 You are not doing any error handling for the creation of Subscription, Device, or Location. 您没有为创建订阅,设备或位置做任何错误处理。

In addition there are several other problems with your create controller method that are beyond the scope of your question. 另外,您的create controller方法还存在其他一些问题,这些问题超出了您的讨论范围。 However, I assume you will find and address those as you work through the code. 但是,我假设您在遍历代码时会找到并解决这些问题。

Lets start with the low hanging fruit - instead of an "unwrapped" hacky TOS you can simply make it a virtual attribute of Company. 让我们从低落的果实开始-可以简单地使其成为Company的虚拟属性,而不是“未包装的” hacking TOS。

class Company < ApplicationRecord
  validates_acceptance_of :terms # makes a virtual attribute if no column exists
end

A major pain point in this controller is that you are doing a bunch of operations after you save the controller that should really be done before. 该控制器的一个主要痛点是,在保存了之前应该真正完成的控制器之后,您需要执行一系列操作。

In Rails when you save a parent record the child records will also be saved. 在Rails中,当您保存父记录时,子记录也将被保存。 You can use this to simplify the logic of this controller: 您可以使用它来简化此控制器的逻辑:

def create
  company = Company.new(company_params)
  subscription = company.subscriptions.new(
    trial: true,  
    charge_date: 1.month.from_now, 
    per_device_price: 5.00, 
    trial_expires: 5.days.from_now
  )
  # ...

  if company.save
    # ...
  else
    # ...
  end  
end

You can use this together with validates_associated so that you would get direct feedback. 您可以将其与validates_associated一起使用,以便获得直接反馈。 You should be able to test it then by: 您应该可以通过以下方式对其进行测试:

require 'rails_helper'

RSpec.describe CompaniesController, :type => :controller do

  before do
    user = build(:user, :company => nil)
    login_with(user)
  end

  describe "#create" do
    let(:valid_attributes) { attributes_for(:company, terms: true) }
    let(:invalid_attributes) { attributes_for(:company, terms: false) }

    describe "with valid attributes" do
      it "creates a company" do
        expect {
          post :create, { company: valid_attributes }
        }.to change(Company, :count).by(+1)
      end
      it "creates a subscription" do
        expect {
          post :create, { company: valid_attributes }
        }.to change(Subscription, :count).by(+1)
        expect(Subscription.last.company).to eq Company.last
      end
    end

    describe "with invalid attributes" do
      it "does not create a company" do
        expect {
          post :create, { company: attributes }
        }.to_not change(Company, :count)
      end
      it "does not creates a subscription" do
        expect {
          post :create, { company: invalid_attributes }
        }.to_not change(Subscription, :count)
      end
    end
  end
end

However I can't help but wonder if part of this logic really should be attached to the user and not the company - if you are keeping track of login devices and locations it should be done in your SessionsController or wherever the user signs in. 但是,我不禁要问,是否应该将此逻辑的一部分确实附加给用户而非公司?-如果您要跟踪登录设备和位置,则应在SessionsController或用户登录的任何地方进行。

The problem is you don't pass terms: '1' as a request param in your test. 问题是您没有通过terms: '1'作为测试中的请求参数。 Instead of that you pass terms: true (which isn't the same as '1') and also you put it inside the company attributes hash. 取而代之的是传递terms: true (与“ 1”不同),然后将其放在company attributes哈希中。 So you need either replace the condition in your action to if company.save && params[:terms] == true or pass '1' in your test. 因此,您需要将操作中的条件替换为if company.save && params[:terms] == true或在测试中通过'1'

That should work 那应该工作

it "should have a subscription" do
  company = build(:company).attributes
  expect {
    post :create, { company: company, terms: '1' }
  }.to change(Subscription, :count).by(1)
end

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

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