I use sphinx and thinking sphinx to search data in my Ruby on Rails application. So, i have this test to check my work:
require 'spec_helper'
require 'thinking_sphinx/test'
describe SearchesController do
render_views
#Start search server in test mode
before(:all) do
ThinkingSphinx::Test.init
ThinkingSphinx::Test.start
end
describe "when signed in" do
before(:each) do
#user = test_sign_in( Factory( :user ) )
#micropost = Factory( :micropost,
:user => #user,
:content => "Test message of user")
ThinkingSphinx::Test.index
get :find_microposts, :q => #micropost.content #Sending data (by :q => "Text")
end
it "should find micropost of user" do
response.should have_selector( "table.microposts",
:content => #micropost.content)
end
end
end
#Stop search server in test mode
after(:all) do
ThinkingSphinx::Test.stop
end
end
Problem is - ThinkingSphinx::Test.index doesn't work. Why?
sphinx.yml
development:
port: 9312
...
test:
port: 9313
...
My system:
Mac OS X
PostgreSQL 9
Rails 3.1.3
Ruby 1.9.3
Sphinx 2.0.3-release (r3043)
Are you using transactional fixtures with RSpec? Because Sphinx can't access records that aren't saved in the database outside of the RSpec context. Also, you should allow a quarter or half a second after indexing for Sphinx to catch up with the indexed data:
sleep(0.25)
All that said, I would recommend stubbing out Sphinx in your controller tests, and only running Sphinx within integration tests (via cucumber/capybara or otherwise).
Related
I am trying to build an app with Cucumber for the first time, using Rails 5.2.6, Rspec, Capybara and Factory bot.
I successfully got through my first feature with scenarios for authentication with devise.
UPDATE
Through a series of troubleshooting steps, detailed below, the problem is that the controller collection #cocktails somehow isn't passing to the view ONLY in the cucumber test.
With the rails server, it passes with no problem.
I checked and #cocktails only appears on the controller and the view. So its not getting overwritten or erased, at least directly.
Its working in the unit RSpec test and also rails server.
How it is not getting passed in the cucumber test? Can anyone see why it wouldn't pass from the controller to the test?
But I hit a snag in my second feature file for CRUD functionality. The very first given step uses FactoryBot to create 2 items, log in the user and go straight to the index for just those two items. But the page shows the view but not the 2 created items as confirmed by using:
puts page.body
in the cucumber file
When I create them on the actual application, it functions correctly, goes straight to the index and displays them.
So I'm trying to figure out how to troubleshoot this. My first thought is to find a way to confirm that FactoryBot created the 2 items. My second thought is to confirm they are actually set to the user. I have tried to use puts to display the two created objects or the user, but I haven't figured out how to call upon them within cucumber.
This is the step file:
Given('I have populated a cocktail list for this User') do
FactoryBot.create(:cocktail,
:user => #registered_user,
:name => "Frank Wallbanger",
:ingredients => "Lots of Booze, a pinch of lime")
FactoryBot.create(:cocktail,
:user => #registered_user,
:name => "Fuzzy Naval Orange",
:ingredients => "Lots of Tequila, a pinch of orange")
end
When('I visit the website and log in') do
expect(page).to have_content("Signed in successfully")
end
Then('I will see the cocktail list') do
puts page.body
expect(page).to have_content("Frank Wallbanger")
expect(page).to have_content("Fuzzy Naval Orange")
end
This is my RSpec unit test file and its green
require "rails_helper"
RSpec.describe CocktailsController do
let(:user) { instance_double(User) }
before { log_in(user) }
describe "GET #index" do
let(:cocktails) { [
instance_double(Cocktail),
instance_double(Cocktail)
] }
before do
allow(user).to receive(:cocktails).and_return(cocktails)
get :index
end
it "looks up all cocktails that belong to the current user" do
expect(assigns(:cocktails)).to eq(cocktails)
end
end
end
This is rails_helper.rb
# 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', __dir__)
# Prevent database truncation if the environment is production
abort("The Rails environment is running in production mode!") if Rails.env.production?
require 'rspec/rails'
# Add additional requires below this line. Rails is not loaded until this point!
# Requires supporting ruby files with custom matchers and macros, etc, in
# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
# run as spec files by default. This means that files in spec/support that end
# in _spec.rb will both be required and run as specs, causing the specs to be
# run twice. It is recommended that you do not name files matching this glob to
# end with _spec.rb. You can configure this pattern with the --pattern
# option on the command line or in ~/.rspec, .rspec or `.rspec-local`.
#
# The following line is provided for convenience purposes. It has the downside
# of increasing the boot-up time by auto-requiring all files in the support
# directory. Alternatively, in the individual `*_spec.rb` files, manually
# require only the support files necessary.
#
# Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f }
# Checks for pending migrations and applies them before tests are run.
# If you are not using ActiveRecord, you can remove these lines.
begin
ActiveRecord::Migration.maintain_test_schema!
rescue ActiveRecord::PendingMigrationError => e
puts e.to_s.strip
exit 1
end
RSpec.configure do |config|
# 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 = true
# You can uncomment this line to turn off ActiveRecord support entirely.
# config.use_active_record = false
# RSpec Rails can automatically mix in different behaviours to your tests
# based on their file location, for example enabling you to call `get` and
# `post` in specs under `spec/controllers`.
#
# You can disable this behaviour by removing the line below, and instead
# explicitly tag your specs with their type, e.g.:
#
# RSpec.describe UsersController, type: :controller do
# # ...
# end
#
# The different available types are documented in the features, such as in
# https://relishapp.com/rspec/rspec-rails/docs
config.infer_spec_type_from_file_location!
# Filter lines from Rails gems in backtraces.
config.filter_rails_from_backtrace!
# arbitrary gems may also be filtered via:
# config.filter_gems_from_backtrace("gem name")
end
Shoulda::Matchers.configure do |config|
config.integrate do |with|
with.test_framework :rspec
with.library :active_record
with.library :active_model
with.library :action_controller
with.library :rails
end
end
RSpec.configure do |config|
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(:truncation)
end
config.around(:each) do |example|
DatabaseCleaner.cleaning do
example.run
end
end
end
require "support/controller_helpers"
RSpec.configure do |config|
config.include Warden::Test::Helpers
config.include Devise::Test::ControllerHelpers, :type => :controller
config.include ControllerHelpers, :type => :controller
end
This is the controller I'm testing:
class CocktailsController < ApplicationController
before_action :set_cocktail, only: %i[ show edit update destroy ]
# GET /cocktails or /cocktails.json
def index
#cocktails = current_user.cocktails
end
# GET /cocktails/1 or /cocktails/1.json
def show
end
# GET /cocktails/new
def new
#cocktail = Cocktail.new
render :new
end
# GET /cocktails/1/edit
def edit
end
# POST /cocktails or /cocktails.json
def create
#cocktail = Cocktail.new cocktail_params.merge(user: current_user)
if #cocktail.save
redirect_to cocktails_path
else
render :new
end
end
# PATCH/PUT /cocktails/1 or /cocktails/1.json
def update
respond_to do |format|
if #cocktail.update(cocktail_params)
format.html { redirect_to #cocktail, notice: "Cocktail was successfully updated." }
format.json { render :show, status: :ok, location: #cocktail }
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: #cocktail.errors, status: :unprocessable_entity }
end
end
end
# DELETE /cocktails/1 or /cocktails/1.json
def destroy
#cocktail.destroy
respond_to do |format|
format.html { redirect_to cocktails_url, notice: "Cocktail was successfully destroyed." }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_cocktail
#cocktail = Cocktail.find(params[:id])
end
# Only allow a list of trusted parameters through.
def cocktail_params
params.require(:cocktail).permit(:name, :ingredients, :user)
end
end
I'm totally new to this so if there are other files you need to see let me know or if I put a block in a bad area.
At the minimum, I'm just looking for how to display the values. The user login runs with the previous steps file where #registered_user was created and it seems to persist with the second step that confirms "Signed in successfully". So I'm down to the items not created or not assigned to the user as my most likely suspects.
Thanks
Here is the index.html.erb view
<h1>Cocktails</h1>
<table>
<thead>
<tr>
<th>Name</th>
<th>Ingredients</th>
<th>User</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% #cocktails.each do |cocktail| %>
<tr>
<td><%= cocktail.name %></td>
<td><%= cocktail.ingredients %></td>
<td><%= cocktail.user_id %></td>
<td><%= link_to 'Show', cocktail %></td>
<td><%= link_to 'Edit', edit_cocktail_path(cocktail) %></td>
<td><%= link_to 'Destroy', cocktail, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to 'New Cocktail', new_cocktail_path %>
<%= button_to 'Log out', destroy_user_session_path, :method => :delete %>
So, after Lam's suggestion below, I tried this attempt to verify.
I went rails console, and tried to run that first FactoryBot.create from the step file. That produced an error that the user #registered_user doesn't exist. Which makes sense in the console. So I can't verify that. BUT, if I created #registered_user from an existing user, that snippet successfully reacted the Frank Wallbanger cocktail. So it seems the FactoryBot is functional. So perhaps is the #registered_user that is the problem?
So I added "puts #registered_user" to the steps file and it did print an active record to the cucumber output.
When I run the rails server and display that view, I see the cocktails listed out. But its still blank in that area of the cucumber output from the "puts page.body" line in the steps file.
UPDATE
Ok, I think I zeroed down to the cause. But not the fix.
In the controller index
#cocktails = current_user.cocktails
Is working on the rails server, but it is NOT populated in the cucumber test.
I added this code to the view
<% if #cocktails.any? %>
<%= #cocktails.first.name %>
<% else %>
<p> none </p>
<% end %>
So why would it not be passing? I tried changing the controller to:
#cocktails = Cocktail.all
And that also doesn't work.
Ok, I finally got it.
I needed to put add visit root_path on the Then step of the step file. And it finally all cleared up.
Then('I will see the cocktail list') do
visit root_path
expect(page).to have_content("Frank Wallbanger")
expect(page).to have_content("Fuzzy Naval Orange")
end
I need a beer.....
You should avoid using Factory Bot when cuking. A better pattern to use with Rails follows.
Have the create and update actions of your controller call a service rather than call Active Record directly.
Your services take new model objects and save or update them. They return a Result hash which contains the following keys [success, model]
If your service succeeds {success: true, model: model}
If your service fails {success: false, model: model(with errrors),... }
Your services create a place for you to deal with business logic that occurs when you create an event e.g. send emails, audit the event, background long running processes etc. etc.
Now you can create things quickly in your Cuking by
a) Initialising a suitable model object with params
b) Calling the service and passing in the model
Ideally you would do this in helper methods that are called in your step defs.
This replaces FactoryBot and at the same time ensures that all the objects you create are created correctly with the same business logic that your application uses. Its very fast (a bit faster than factory bot and way faster than using the UI). It avoids you having to duplicate business logic by configuring FactoryBot to match your applications business logic. And finally it provides far more flexibility in applying business logic than relying solely on ActiveModel callbacks.
This gives you a pattern for CRUD. When you start with something new you will have
Scenario: Create a foo
Given ...
When I create a new foo
Then I should have a new foo
This will drive the development of the model, controller, controller#new, view#new, form, controller#create, controller#show view#show
Once you have these and have followed the pattern above you can then use foo in your givens e.g
Given there is a foo
When I bar with my foo
...
and you can create the foo with no user interaction and no need for Factories.
e.g
module FooSH
def create_foo(foo: )
Result = CreateFooService.new(foo: foo).call
Result.model
end
def create_default_foo
Result = CreateFooService.new(foo: Foo.new(default_foo_params).call
Result.model
end
def default_foo_params
{
}
end
end
World FooSH
and then your step
Given 'there is a foo' do
#foo = create_default_foo
end
You can apply your own styles and patterns to this and expand on object creation in a number of ways. You can see an example of this in this rather old repo https://github.com/diabolo/cuke_up
I've written some RSpec tests that successfully create objects with :let statements. However, the test environment doesn't maintain the associations that function properly everywhere else. Below is an example of a class that would turn up a NoMethodError (undefined method `money' for nil:NilClass). Money is a column in Inventory. Any thoughts?
class Inventory < ActiveRecord::Base
belongs_to :character
def self.return_money(character)
character.inventory.money
end
end
And here's a corresponding example for a spec doc:
require 'spec_helper'
describe 'Test methods' do
let(:trader) {
Character.create(
name: "Trader",
location_id: 1)
}
let(:trader_inventory) {
Inventory.create(
character_id: trader.id,
storage_capacity: 50000,
money: 20000,
markup: 1.35)
}
it "test method" do
expect(Inventory.return_money(trader)).to eq(100)
end
end
There is no reason this shouldn't work. RSpec isn't special, it's just regular Ruby code. You can confirm this by moving all of your code into a single file, something like:
ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
ActiveRecord::Base.logger = Logger.new(STDOUT)
class Inventory < ActiveRecord::Base
end
class Character < ActiveRecord::Base
has_one :inventory
end
describe 'test' do
it 'works' do
puts Character.first.inventory.money.inspect
end
end
Guesses as to what may be broken:
money is a composite field or something like that. Can you post your database schema?
Library files aren't being loaded correctly. Use puts $LOADED_FEATURES to verify that all the files that should be required have been.
I am trying to test a ruby authentication app using minitest and webrat but get errors.
Tests like visit '/' fail with an error Status 200 expected but was 404.
Tests containing code like fill_in :email, :with => "first#company.com" fail with error Could not find field: :email.
I read several sinatra, testing and webrat documents and forums. Some of them were old and suggested stuff like Sinatra::Default, but github.com/brynary/webrat/wiki/sinatra, Building a Sinatra App Driven By Webrat Tests and Learning From the Masters: Sinatra Internals are new, yet they still fail.
Basically, I didn't like sentence-like syntax of rspec, cucumber etc but do want to do behaviour driven development. I really like the minitest syntax, both tests and output and that is why I choose webrat for BDD. If I'm wrong about expecting webrat to fulfill acceptance testing requirements, please simply tell me that I should use this framework or that one.
Apart from that, the first parts of the main file and test file are below. I hope someone can explain me, what I am missing?
test_file
require "test/unit"
require "minitest/autorun"
require "rack/test"
require 'webrat'
require_relative "../lib/kimsin.rb"
Webrat.configure do |config|
config.mode = :rack
end
ENV["RACK_ENV"] = "test"
class KimsinTests < Test::Unit::TestCase
include Rack::Test::Methods
include Webrat::Methods
include Webrat::Matchers
def app
Sinatra::Application.new
end
def test_create_user
visit "/user/new"
fill_in :username, :with => "first#company.com"
fill_in :password, :with => "abC123?*"
fill_in :confirm_password, :with => "abC123?*"
click_link "Register"
assert 201, last_response.status, "Status 201 expected but was #{last_response.status}.\n#{error}"
assert_contain /Logged in as first#company.com./, "No user created"
assert_contain /Logout/, "Logout link not present"
end
main_file
require "sinatra"
require "erb"
require_relative "../lib/kimsin/version"
require_relative "../lib/kimsin/user"
class Kimsin < Sinatra::Application
use Rack::Session::Pool, :expire_after => 2592000
set :session_secret, BCrypt::Engine.generate_salt
configure :development do
DataMapper.auto_migrate!
end
get "/" do
if session[:user_id]
user = User.get session[:user_id]
email = user.email
erb :index, :locals => { :email => email }
else
email = nil
erb :index, :locals => { :email => email }
end
end
Using Sinatra with Webrat should work fine. I think that the errors that you are seeing are caused by the following method (around line 18 in your test file):
def app
Sinatra::Application.new
end
This is setting up the Sinatra::Application base class to run your tests against when you really need to set up your own subclass Kimsin (because you are creating a modular style Sinatra app), i.e.
def app
Kimsin.new
end
The 404 errors and missing fields are happening because Sinatra::Application doesn't define any of the routes you are testing.
You might also like to take a look at Capybara if you are looking for similar alternatives to Webrat.
I'm writing some specs that test the template files in a gem that has generators for Rails. I'd love to access to "admin_layout.html.erb" in the rspec spec below:
require 'spec_helper'
describe "admin_layout.html.erb" do
it "has page title Admin" do
HERES WHERE I WOULD LOVE TO HAVE ACCESS TO "admin_layout.html.erb" AS A VARIABLE
end
end
You can use self.class.description to get this info:
it "has page title Admin" do
layout = self.class.description
# => "admin_layout.html.erb"
end
However, keep in mind this will only put out the first parent's description. So if you have contexts in your describe block, then the examples within the contexts would give the context name for self.class instead of the describe block's name. In that case, you could use metadata:
describe "admin_layout.html.erb", :layout => "admin_layout.html.erb"
context "foo" do
it "has page title Admin" do
layout = example.metadata[:layout]
end
end
end
In case you want the top-level description, you can use self.class.top_level_description:
RSpec.describe "Foo", type: :model do
context "bar" do
it "is part of Foo" do
self.class.top_level_description
# => "Foo"
end
end
end
I'm trying to set the environment for testing using Selenium and selenium-client gem.
I prefer unit test style over RSpec style of tests.
Do I have to build my own system for reporting then?
How can I add exception handling without having begin-rescue-end in each test? Is there any way to do that using mixins?
I'm not sure I understand what your question means in terms of reporting but the selenium-client gem handles both BDD and UnitTesting.
Below is code copied from the rubyforge page:
require "test/unit"
require "rubygems"
gem "selenium-client", ">=1.2.16"
require "selenium/client"
class ExampleTest < Test::Unit::TestCase
attr_reader :browser
def setup
#browser = Selenium::Client::Driver.new \
:host => "localhost",
:port => 4444,
:browser => "*firefox",
:url => "http://www.google.com",
:timeout_in_second => 60
browser.start_new_browser_session
end
def teardown
browser.close_current_browser_session
end
def test_page_search
browser.open "/"
assert_equal "Google", browser.title
browser.type "q", "Selenium seleniumhq"
browser.click "btnG", :wait_for => :page
assert_equal "Selenium seleniumhq - Google Search", browser.title
assert_equal "Selenium seleniumhq", browser.field("q")
assert browser.text?("seleniumhq.org")
assert browser.element?("link=Cached")
end
end
As for exception handling, UnitTesting handles the exceptions with an Error message.
That being said, I may have misunderstood your question.
Initial build of Extent is available for Ruby. You can view the sample here. Latest source is available at github.
Sample code:
# main extent instance
extent = RelevantCodes::ExtentReports.new('extent_ruby.html')
# extent-test
extent_test = extent.start_test('First', 'description string')
# logs
extent_test.log(:pass, 'step', 'details')
extent.end_test(extent_test)
# flush to write everything to html file
extent.flush