Hot questions for Using Spree in rspec

Question:

I am trying to use the Spree 2.3 route helpers in Rspec 3.0. In the main app, I can access them by prefixing spree., like so:

spree.admin_login_path => 'spree/admin/user_sessions#new'

However I can't seem to access them in Rspec.

#rspec error
undefined local variable or method `spree_admin_login_path'

I've found reference to including the helpers in the rails_helper file, but this throws an error

# app/spec/rails_helper.rb
RSpec.configure do |config|
  config.include Spree::Core::UrlHelpers
end

# configuring the above results in the following
app/spec/rails_helper.rb:21:in `block in <top (required)>': uninitialized constant Spree (NameError)

How do I access the spree routes given in $ rake routes in my tests?


Answer:

After digging through the Spree code I was able to put together this setup for my rails_helper file that lets me use spree routes such as spree.admin_login_path in my spec files:

# app/spec/rails_helper.rb
require 'spree/testing_support/url_helpers'

RSpec.configure do |config|
  config.include Spree::TestingSupport::UrlHelpers
end

I'm sure there's a smoother way to include all of Spree's test helpers, and I'd love to hear about it from someone who knows.

Question:

I am trying to stub Spree's current_user method in Rspec 3.0, Capybara 2.3, to no avail. My goal is to test the page for content that should only appear when the user is logged in. How can I stub the spree_current_user helper in a feature spec?

Feature Spec

#spec/features/spree_variants_spec.rb
before(:each) do
  user = FactoryGirl.create(:user, first_name: "First name")
  helper.stub :spree_current_user => user # does not work
end

Controller

class Designers::SpreeVariantsController < ApplicationController
  def create
    ...
    @variant.attribute  = spree_current_user.first_name  #line 14
    ...
  end
end

Error

Failure/Error: click_button 'Create'
 NoMethodError:
   undefined method `first_name' for nil:NilClass
 # ./app/controllers/designers/spree_variants_controller.rb:14:in `create'

In stubbing the method, I have also tried:

#2
Designers::SpreeVariantsController.stub :spree_current_user => instance_double(Spree.user_class, :has_spree_role? => true, :last_incomplete_spree_order => nil, :spree_api_key => 'fake', first_name: "First name")

#3
self.stub :spree_current_user => user # same error

#4
helper.stub :spree_current_user => user # does not recognize 'helper'

Answer:

RSpec's request and feature specs are designed for testing the full stack, not individual controllers, so you can't get an instance of a controller. It might meet your needs to stub the method on all instances of that controller:

allow_any_instance_of(Designers::SpreeVariantsController).to receive(:spree_current_user).and_return(user)

However,

  • according to the RSpec documentation, request specs don't support current versions of Capybara. If it's working for you in request specs, so far so good, but you may be going down a deprecated path. The up-to-date way of writing specs that exercise your entire stack is feature specs.

  • Request specs and feature specs are for testing the entire stack, not unit testing. Stubbing in them is a little fishy. You should consider writing a few request or feature specs in which the user logs in through the UI, and testing details in controller specs where you should feel free to stub whatever you like.

Question:

I am trying to create test to admin panel. But it fails while the program try to log_in.

Failures:

1) Visit products login works correctly Failure/Error: expect(page).to have_content("Logged in successfully")

expected to find text "Logged in successfully" in "Login\nAll departments\nHome\nCart: (Empty)\n \nInvalid email or password.\nLogin as Existing Customer\nRemember me\nor Create a new account | Forgot Password?" # ./spec/features/home_spec.rb:14:in `block (2 levels) in '

The password, and the email are correct for admin. I found solutions in other posts, like adding configuration to capybara, but it still fails.

spec_helper

require 'capybara/rspec'
require 'rails_helper'
require 'spree/testing_support/controller_requests'
require 'capybara/rails'

Capybara.app_host = "http://localhost:3000"
Capybara.server_host = "localhost"
Capybara.server_port = "3000"

_spec.rb

require "spec_helper"

RSpec.describe 'Visit products' do
    it 'login works correctly' do
        visit spree.admin_path
        fill_in "spree_user[email]", with: "piotr.wydrzycki@yahoo.com"
    fill_in "spree_user[password]", with: "password"
    click_button Spree.t(:login)
        expect(page).to have_content("Logged in successfully")
    end
end

Answer:

Since the page is showing "Invalid email or password" either the email or password aren't correct, or the user for the test isn't being created correctly. Since you don't show the creation of any test users in your test it's most likely there aren't any. When running in test mode the app doesn't use your development database, it has its own database and you need to create all the objects (like users) you expect to exist for the test. You can do this by using fixtures or something like factory_bot to create users before each test.

Additionally, there should be no need to set server_host,server_port, or app_host in your situation.

Question:

I am trying to create a rspec test for custom validation in a spree extension(like a gem) I need to validate uniqueness of a variants option values for a product (all Spree models) Here is the basic structure of models(Although they are part of spree, a rails based e-commerce building):

class Product
  has_many :variants
  has_many :option_values, through: :variants #defined in the spree extension, not in actual spree core
  has_many :product_option_types
  has_many :option_types, through: :product_option_types
end


class Variant
  belongs_to :product, touch: true
  has_many :option_values_variants
  has_many :option_values, through: option_values
end

class OptionType
  has_many :option_values
  has_many :product_option_types
  has_many :products, through: :product_option_types
end

class OptionValue
  belongs_to :option_type
  has_many :option_value_variants
  has_many :variants, through: :option_value_variants
end

So I have created a custom validation to check the uniqueness of a variants option values for a certain product. That is a product(lets say product1) can have many variants. And a variant with option values lets say (Red(Option_type: Color) and Circle(Option_type: Shape)) have to unique for this product

Anyway this is the custom validator

 validate :uniqueness_of_option_values

 def uniqueness_of_option_values
 #The problem is in product.variants, When I use it the product.variants collection is returning be empty. And I don't get why.
   product.variants.each do |v|
   #This part inside the each block doesn't matter though for here.
     variant_option_values = v.option_values.ids
     this_option_values = option_values.collect(&:id)
     matches_with_another_variant = (variant_option_values.length == this_option_values.length) && (variant_option_values - this_option_values).empty?
     if  !option_values.empty? &&  !(persisted? && v.id == id) && matches_with_another_variant
       errors.add(:base, :already_created)
     end
   end
 end

And finally here are the specs

require 'spec_helper'

describe Spree::Variant do

  let(:product) { FactoryBot.create(:product) }
  let(:variant1) { FactoryBot.create(:variant, product: product) }

  describe "#option_values" do
    context "on create" do
      before do
        @variant2 = FactoryBot.create(:variant, product: product, option_values: variant1.option_values)
      end

      it "should validate that option values are unique for every variant" do
      #This is the main test. This should return false according to my uniqueness validation. But its not since in the custom uniqueness validation method product.variants returns empty and hence its not going inside the each block.
        puts @variant2.valid?


        expect(true).to be true #just so that the test will pass. Not actually what I want to put here
      end
    end
  end
end

Anybody know whats wrong here. Thanks in advance


Answer:

I have a guess at what's happening. I think a fix would be to change your validation with the following line:

product.variants.reload.each do |v|

What I think is happing is that when you call variant1 in your test, it is running the validation for variant1, which calls variants on the product object. This queries the database for related variants, and gets an empty result. However, since variant2 has the same actual product object, that product object will not re-query the database, and remembers (incorrectly) that its variants is an empty result.

Another change which might make your test run is to change your test as follows:

before do
  @variant2 = FactoryBot.create(:variant, product_id: product.id, option_values: variant1.option_values)
end

It is subtle and I'd like to know if it works. This sets the product_id field on variant2, but does not set the product object for the association to be the actual same product object that variant1 has. (In practice this is more likely to happen in your actual code, that the product object is not shared between variant objects.)

Another thing for your correct solution (if all this is right) is to do the reload but put all your save code (and your update code) in a transaction. That way there won't be a race condition of two variants which would conflict, because in a transaction the first must complete the validation and save before the second one does its validation, so it will be sure to detect the other one which just saved.

Some suggested debugging techniques:

  • If possible, watch the log to see when queries are made. You might have caught that the second validation did not query for variants.
  • Check the object_id. You might have caught that the product objects were in fact the same object.
  • Also check new_record? to make sure that variant1 saved before you tested variant2. I think it does save, but it would have be nice to know you checked that.

Question:

I would like to build a test suite for my Spree app using FactoryGirl and RSpec. Should I write tests from scratch, or can I somehow make use of an existing resource so I don't end up duplicating code?

There are many _spec.rb files in spree/core/spec/models/spree. It would be great if could run rspec spec and run the core spree test suite plus any tests I write.

The Spree documentation recommends running bundle exec rake test_app to "build the appropriate test application inside of your spec directory." When I run that command, I receive this error message:

rake aborted! Don't know how to build task 'ecommerce' (see --tasks)

Spree is mounted in config/routes.db using this line: mount Spree::Core::Engine, at: '/'.

I would love to have the "appropriate files" so that I don't have to write many test that have been already written, but I am stumped. Thank you for any tips.


Answer:

From the Spree documentation:

Spree consists of several different gems (see the Source Code Guide for more details.) Each of these gems has its own test suite which can be found in the spec directory. Since these gems are also Rails engines, they can't really be tested in complete isolation - they need to be tested within the context of a Rails application.

You can easily build such an application by using the Rake task designed for this purpose, running it inside the component you want to test:

$ bundle exec rake test_app

In other words, if you want to test Spree itself, build a new Rails application for that specific purpose.

I doubt that is really what you want to do. If you write tests for your own application, even if it uses Spree, you don't need to test Spree itself, just the code that you write. Presumably the authors of Spree run those tests before releasing each new version into the wild.