Search code examples
ruby-on-railsdatabaserspeccapybaraspork

Capybara with Javascript (Rspec, Spork, and FactoryGirl to Boot)


I've got an asynchronous javascript function I need to test with capybara. I understand that I have to use the js: true option on the tests and set config.use_transactional_fixtures = false, then throw in some database_cleaner, but I still can't get it working.

The cart_spec runs through the whole process of adding an item to the cart and purchasing it (so it is nested pretty deep).

checkout.html.erb uses the stripe library to handle credit cards. That has the javascript functionality I need to test.

When I run the tests with the js: true option, I get a test failure that 'Add to Cart' doesn't exist on the sports_url, indicating the sports aren't being saved to the database.

When I run the tests without the js: true option, all the tests pass except for it "should warn user that cc number is invalid" do because that is where the javascript is.

I'm running spork and have tried restarting the server.

Edit

The problem is that my test is running at the domain www.example.com, so my sports_url is being directed to www.example.com/sports. I've asked a new question here. If I access it with sports_path, it works great.

Here are the files:

cart_spec:

require "spec_helper"

describe "Cart" do
  before do
    @user = FactoryGirl.create(:user)
    @cart = @user.carts.create!
  end

  describe "using stripe" do
      before do
        @sport = FactoryGirl.create(:sport)
      end

      describe "user adds sport to cart", js: true do
        before do
          visit sports_url
          click_link "Add to Cart"
        end

        it "should be checkout page" do
          page.should have_content("Total")
        end

        describe "user clicks checkout" do
          before do
            click_button "Checkout"
          end

          it "should redirect user to sign in form" do
            page.should have_selector('h2', text: "Sign in")
          end

          describe "user logs on" do
            before do
              fill_in "Email", with: @user.email
              fill_in "Password", with: @user.password
              click_button "Sign in"

            end

            it "should be on checkout page" do
              page.should have_selector('h2', text: "Checkout")
            end

            describe "user fills in form" do
              context "with invalid cc number" do
                before do
                  fill_in "card-number", with: 42
                  click_button "Submit Payment"
                end

                it "should warn user that cc number is invalid" do
                  page.should have_content("Your card number is invalid")
                end
              end
            end

          end
        end
      end
  end

  describe "GET /carts/checkout" do
    subject { @cart }

    it { should respond_to(:paypal_url) }
    it { should respond_to(:apply_discount) }

    it "paypal_url contains notification" do
      @cart.paypal_url(root_url, payment_notifications_url).should include("&notify_url=http%3A%2F%2Fwww.example.com%2Fpayment_notifications")
    end

    it "paypal_url contains invoice id" do
      @cart.paypal_url(root_url, payment_notifications_url).should match /&invoice=\d+&/
    end

    it "paypal_url contains return url" do
      @cart.paypal_url(root_url, payment_notifications_url).should include("&return=http%3A%2F%2Fwww.example.com")
    end
  end

  describe "GET /carts/discount" do
    it "should apply discount to all line items" do
        @cart.line_items.build(:unit_price => 48)

        @cart.apply_discount

        @cart.line_items.each do |lineItem|
          lineItem.unit_price.should == 9.99
        end
    end
  end

end

checkout.html.erb:

<h2>Checkout</h2>
<span class="payment-errors"></span>
<form action="" method="POST" id="payment-form">
    <div class="form-row">
        <label>Card Number</label>
        <input type="text" size="20" autocomplete="off" id ="card-number" class="card-number"/>
    </div>
    <div class="form-row">
        <label>CVC</label>
        <input type="text" size="4" autocomplete="off" class="card-cvc"/>
    </div>
    <div class="form-row">
        <label>Expiration (MM/YYYY)</label>
        <input type="text" size="2" class="card-expiry-month"/>
        <span> / </span>
        <input type="text" size="4" class="card-expiry-year"/>
    </div>
    <button type="submit" class="submit-button">Submit Payment</button>
</form>

<script type="text/javascript" src="https://js.stripe.com/v1/"></script>
<script type="text/javascript">
    Stripe.setPublishableKey('pk_xIm00GVAKVLMWmfeR2J8GlmeHcyhL');

    $(document).ready(function() {
      $("#payment-form").submit(function(event) {
        // disable the submit button to prevent repeated clicks
        $('.submit-button').attr("disabled", "disabled");

        Stripe.createToken({
            number: $('.card-number').val(),
            cvc: $('.card-cvc').val(),
            exp_month: $('.card-expiry-month').val(),
            exp_year: $('.card-expiry-year').val()
        }, stripeResponseHandler);

        // prevent the form from submitting with the default action
        return false;
      });
    });

    function stripeResponseHandler(status, response) {
        if (response.error) {
            $('.submit-button').removeAttr("disabled");
            //show the errors on the form
            $(".payment-errors").html(response.error.message);
        } else {
            var form$ = $("#payment-form");
            // token contains id, last4, and card type
            var token = response['id'];
            // insert the token into the form so it gets submitted to the server
            form$.append("<input type='hidden' name='stripeToken' value='" + token + "'/>");
            // and submit
            form$.get(0).submit();
        }
    }
</script>

spec_helper:

require 'rubygems'
require 'spork'

Spork.prefork do
  # Loading more in this block will cause your tests to run faster. However, 
  # if you change any configuration or code from libraries loaded here, you'll
  # need to restart spork for it take effect.
  # This file is copied to spec/ when you run 'rails generate rspec:install'
  ENV["RAILS_ENV"] ||= 'test'
  require File.expand_path("../../config/environment", __FILE__)
  require 'rspec/rails'
  require 'rspec/autorun'

  # Requires supporting ruby files with custom matchers and macros, etc,
  # in spec/support/ and its subdirectories.
  Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}

  RSpec.configure do |config|
    # == Mock Framework
    #
    # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
    #
    # config.mock_with :mocha
    # config.mock_with :flexmock
    # config.mock_with :rr
    config.mock_with :rspec

    # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
    config.fixture_path = "#{::Rails.root}/spec/fixtures"

    # If you're not using ActiveRecord, or you'd prefer not to run each of your
    # examples within a transaction, remove the following line or assign false
    # instead of true.
    config.use_transactional_fixtures = false

    # If true, the base class of anonymous controllers will be inferred
    # automatically. This will be the default behavior in future versions of
    # rspec-rails.
    config.infer_base_class_for_anonymous_controllers = false

    config.before(:suite) do
        DatabaseCleaner.strategy = :truncation
    end

    config.before(:each) do
        DatabaseCleaner.start
    end

    config.after(:each) do
        DatabaseCleaner.clean
    end
  end
end

Spork.each_run do
  # This code will be run each time you run your specs.

end

Solution

  • The problem was I was using visit <route>_url. The default domain for rails tests is exmaple.com, so my browser was trying to access www.example.com/sports, which is kindly reserved by the international internet naming committee.

    I changed those to visit <route>_path and things work great.