2

I am facing a wired issue, factoryBot is creating a record in Db, but when capybara try to access it, there is no record in HTML.

I tried debugging with "byebug", on prompt, when I say @topics => It gives me Nil data. (@topic is instance variable in topics_controller.rb -> index method )

If I do "Topic.all.first" it will show me correct record of Topic with an expected random name that is -> "Toys & Garden" If I do "random_topic.name" -> "Toys & Garden"

I have somewhat similar setup in other feature i.e "account creation feature", it is working fine in there. Any pointer or help would be highly appreciated.

My factory file is

FactoryBot.define do
  factory :topic do
    name { Faker::Commerce.department(2, true) }
    archived false
  end
end

My Feature spec file looks like below

require 'rails_helper'

describe "topics" do
  let(:user) {create(:user)} 

  before do 
    sign_user_in(user)  #support fuction to sign user in

  end 

  it "allows topics to be edited" do
    random_topic = create(:topic)
    visit topics_path   # /topics/ 
    expect(page).to have_text random_topic.name   # Error1 

    click_edit_topic_button random_topic.name  # Another support fuction 
    random_topic_name_2 = Faker::Commerce.department(2, true)
    fill_in "Name", with: random_topic_name_2
    check "Archived"
    click_button "Update Topic"

    expect(page).to have_text "Topic updated!"
    expect(page).to have_text random_topic_name_2
  end
end

I get the error on line marked as "Error 1" , please note that "Toys & Garden" is sample name generated by Faker Gem.

Failure/Error: expect(page).to have_text random_topic.name
       expected #has_text?("Toys & Garden") to return true, got false

my Rails helper(rails_helper.rb) file setup is as below.

# This file is copied to spec/ when you run 'rails generate rspec:install'

require 'spec_helper'
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
abort("The Rails environment is running in production mode!") if Rails.env.production?
require 'rspec/rails'
require 'database_cleaner'
require 'capybara/rspec'
require 'shoulda/matchers'
require 'email_spec'
require "email_spec/rspec"


Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }

ActiveRecord::Migration.maintain_test_schema!

Shoulda::Matchers.configure do |config|
  config.integrate do |with|
    with.test_framework :rspec
    with.library :rails
  end
end

# This is for setting up Capybara right host.
# https://stackoverflow.com/questions/6536503/capybara-with-subdomains-default-host 
def set_host (host)
  default_url_options[:host] = host
  Capybara.app_host = "http://" + host
end

RSpec.configure do |config|

  config.include Capybara::DSL

  config.include Rails.application.routes.url_helpers

  config.include FactoryBot::Syntax::Methods
  config.include EmailSpec::Helpers
  config.include EmailSpec::Matchers

  config.order = "random"

  config.use_transactional_fixtures = false

  config.before(:each) do
    set_host "lvh.me:3000"
  end

  config.before(:suite) do
    DatabaseCleaner.clean_with(:truncation)
  end

  config.before(:each) do
    DatabaseCleaner.strategy = :transaction
  end

  config.before(:each, :js => true) do
    DatabaseCleaner.strategy = :truncation
  end

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

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

end

My Topics Controller file is something like below

class TopicsController < ApplicationController

  layout 'dashboard'
  before_action :authenticate_user!


  def index
    @topics = Topic.all
  end

end

Updated with @smallbutton.com comments, but issue continues.

SOLVED

I am using apartment gem and hence topic was getting created in public schema while test was looking into a respective tenant.

As per @thomas suggestion, I modified the code:

  before do 
    set_subdomain(account.subdomain)
    sign_user_in(user)  #support fuction to sign user in
    Apartment::Tenant.switch!(account.subdomain)
  end 
Solid
  • 307
  • 1
  • 12
  • can you include the controller methods? – max pleaner Feb 13 '18 at 07:24
  • Do you mean Topics controller method? – Solid Feb 13 '18 at 07:44
  • Please show your Capybara settings (current_driver, javascript_driver, default_driver, etc). Also, what version of Rails are you using? – Thomas Walpole Feb 13 '18 at 15:25
  • I dont have any specific settings for my capybara setup in rails_helper or spec_helper files. I am using rails 5.1.4. Do I have to add these settings explicitly? – Solid Feb 13 '18 at 17:03
  • @ThomasWalpole I just went through docs here https://github.com/teamcapybara/capybara#using-capybara-with-rspec As I have not mentioned explicitly, it must be using default Rack driver . – Solid Feb 13 '18 at 17:06
  • @Solid Have you set `Capybara.server` to anything? and do you have any `puma` output in your tests output? If so please add it to the question. – Thomas Walpole Feb 13 '18 at 17:12
  • @ThomasWalpoleThanks again for helping with this. I have updated the full version of my rails_spec file. I have not set capybara.server to anything specific. There is no puma specific output, only error I see is the one I posted in question. – Solid Feb 13 '18 at 17:22

4 Answers4

4

When this happens it's generally caused by one of a few things

  1. The record isn't actually being created

    From your code it doesn't appear to be that

  2. The record is being created in one transaction while the app is running in a different transaction.

    You appear to have database_cleaner configured correctly, and this shouldn't be an issue in your case, however you should be using - https://github.com/DatabaseCleaner/database_cleaner#rspec-with-capybara-example - rather than the configuration you have (which depends on the :js metadata rather than checking which driver is actually being used)

  3. The user has configured Capybara to hit a different server than the one the test is actually running on.

    Here we appear to have an issue, since you're configuring Capybara.app_host to be 'lvh.me:3000'. Port 3000 is what your dev app is generally run on, while Capybara (by default) starts your app on a random port for tests. This probably means your tests are actually running against your dev app/db instance which has nothing to do with the test app/db instance, and therefore the tests won't see anything you create in your tests. Remove the setting of Capybara.app_host unless you have an actual need for its setting (in which case remove the port from your app_host setting and enable Capybara.always_include_server_port)

This all being said, since you're using Rails 5.1+ database_cleaner should not be needed anymore. You should be able to remove all of database_cleaner, reenable use_transactional_fixtures, and set Capybara.server = :puma and have things work fine (still would need to fix the app_host setting)

Thomas Walpole
  • 48,548
  • 5
  • 64
  • 78
  • I started with 2nd Option you mentioned. Fixed the configuration with the example provided at the link. Then also removed a port from set_host. Also stopped my development server just to make sure that it won't be used. (I highly doubt that as it was the case though, as my development database must have got screwed otherwise.) Added configuration setting "Capybara.always_include_port" (I do need it as my app is multi-tenant app with apartment gem ) Still the same issue. (BTW, I don't know if its relevant but I am using live guard for continuously running my test suit ) – Solid Feb 13 '18 at 18:23
  • Then I started with 3rd Option you mentioned. Removed port, stopped dev server to be extra careful. Removed DatabaseCleaner settings, enable use_transactional_fixtures to true. Also setup the Capybara.server = :puma. still the same issue. No change. – Solid Feb 13 '18 at 18:29
  • @Solid Having the port set on app_host wouldn't have screwed your dev database since the creation/deletion of records for setup of the tests wouldn't have accessed the dev DB, only the browser would have connected to you dev app/db. Anyway, if you've done everything I suggested correctly it should work. Is this a publicly accessible repo? or can you provide me access to take a look? – Thomas Walpole Feb 13 '18 at 18:38
  • I tried all the options you mentioned, and still the error persisted. This is not a publicly accessible repo, its private repo on gitlab, let me know how can I share access with you? Thanks again for all the help! – Solid Feb 13 '18 at 19:29
  • Thomas - do you have gitlab account? I have repo on gitlab.com! – Solid Feb 13 '18 at 19:32
  • Added, let me know if you have any question about setting it up! – Solid Feb 13 '18 at 19:41
  • oh, sorry, Can you check now? – Solid Feb 13 '18 at 19:51
  • @Solid - You're using the `apartment` gem which adds a layer of complexity. You're creating the `random_topic` in the "public" tenant, but the topics controller displays the topics from the users tenant. You need to create your records in the correct tenant. – Thomas Walpole Feb 13 '18 at 20:18
  • But if you look at Accounts.rb factory, i.e account_with_schema kind of takes care of switching to right tenant. Let me check if there is explicit switching correct tenant I can do. – Solid Feb 13 '18 at 20:28
  • @Solid - the `account_with_schema` factory switches back to the public tenant after creating the account -- so there is no tenant set when you create the `random_topic` – Thomas Walpole Feb 13 '18 at 20:34
  • Yes, you are right. That was the issue. I moved random_topic = create(:topic) into scenario section and then explicit did the tenant switch in before at the top. I was assuming sign_user_in(user) would have done tenant switch as but I guess I was wrong in there. Thanks @thomas! This was great help! – Solid Feb 13 '18 at 20:39
3

Your record isn't persisted when you visit the page, so it's normal that it return false.

Try the following :

require 'rails_helper'

describe "topics" do
  let(:user) {create(:user)}
  let!(:random_topic) {create(:topic)}

  before do 
    sign_user_in(user)  #support fuction to sign user in
    visit topics_path   # /topics/ 
  end 

  it "allows topics to be edited" do
    expect(page).to have_text random_topic.name   # Error1 

    click_edit_topic_button random_topic.name  # Another support fuction 
    random_topic_name_2 = Faker::Commerce.department(2, true)
    fill_in "Name", with: random_topic_name_2
    check "Archived"
    click_button "Update Topic"

    expect(page).to have_text "Topic updated!"
    expect(page).to have_text random_topic_name_2
  end
end

Note that random_topic is extracted in a let! (more info about the difference between let and let! : https://relishapp.com/rspec/rspec-core/v/2-5/docs/helper-methods/let-and-let !

Philippe B.
  • 364
  • 2
  • 8
  • Thanks @philippe-b, But I made the change you suggested, but still same problem, no change. – Solid Feb 13 '18 at 10:08
  • You could try to run helper methods of capybara, to check what you browser really sees with : _screenshot_and_open_image_ or _save_and_open_page_ ? – Philippe B. Feb 13 '18 at 10:49
  • I am not able to run those helper methods, its getting error "undefined methods" . Any specific way to use these methods? – Solid Feb 13 '18 at 12:33
  • It comes with Capybara : http://www.rubydoc.info/github/jnicklas/capybara/Capybara%2FSession%3Asave_and_open_page :) – Philippe B. Feb 13 '18 at 13:40
  • Ohh, Sorry about that. I can see the sample html, I dont see the random_topic in the list. When I debug with byebug, Topic.all.first -> Returns me Correct random topic object newly created. random_topic.nam -> Return me correct random topic name (matches with ActiveRecord data) @topics -> return nil / empty. HTML page also dont show record in list. It is possible that Factorybot is creating the record in a model, database_cleaner is clearing the data before Capybara can make a request? – Solid Feb 13 '18 at 13:48
  • Well I see that Thomas helped you. Indeed if you use Apartment gem, you have to switch into the tenant if your user! – Philippe B. Feb 15 '18 at 09:05
  • I would suggest to extract that login logic into a helper, that you could re-use for all you tests! – Philippe B. Feb 15 '18 at 09:10
0

You should create the record before you visit the page. Currently you do it the other way around so your record cannot appear.

smallbutton
  • 3,377
  • 15
  • 27
  • You were right, I made the change i.e random_topics = create(:topic) visit topics_path expect(page).to have_text random_topic.name but still same problem, I can see the value in DB, but @topics is stil nill and hence no record on page too! – Solid Feb 13 '18 at 10:13
0

People having this kind of issue should definitly try this:

instance.reload after clicking and before using "expect".

This solved my issue I've been stuck on for a whole day ...

Sunamin34
  • 174
  • 1
  • 7