Browse files

Add a feature for the javascript truncation strategy #166

  • Loading branch information...
1 parent 14a9e4c commit 9b03436b681d3d9dbe821e54f5479ceea9bf5525 @mattwynne mattwynne committed Nov 3, 2011
View
63 features/choose_javascript_database_strategy.feature
@@ -0,0 +1,63 @@
+@announce
+Feature: Choose javascript database strategy
+
+ When running a scenario with the @javascript tag, Capybara will fire up a web server
+ in a separate thread to your cukes. By default, this means ActiveRecord will give it a
+ separate database connection, which in turn means data you put into your database from
+ Cucumber step definitions (e.g. using FactoryGirl) won't be visible to the web server
+ until the database transaction is commited.
+
+ So if you use a transaction strategy for cleaning up your database at the end of a scenario,
+ it won't work for javascript scenarios by default.
+
+ There are two ways around this. One is to switch to a truncation strategy for javascript
+ scenarios. This is slower, but more reliable.
+
+ The alternative is to patch ActiveRecord to share a single database connection between
+ threads. This means you still get the speed benefits of using a transaction to roll back
+ your database, but you run the risk of the two threads stomping on one another as they
+ talk to the database.
+
+ Right now, the default behavior which works for 80% of cucumber-rails users is to use
+ the shared connection patch, but you can override this by telling cucumber-rails which
+ strategy to use for javascript scenarios.
+
+ Background:
+ Given I have created a new Rails 3 app and installed cucumber-rails
+ And I have a "Widget" ActiveRecord model object
+ And I append to "features/env.rb" with:
+ """
+ Cucumber::Rails::Database.javascript_strategy = :truncation
+ """
+
+ Scenario: Set the strategy to trunction and run a javascript scenario.
+ Given I write to "features/widgets.feature" with:
+ """
+ @javascript
+ Feature:
+ Background:
+ Given I have created 2 widgets
+
+ Scenario:
+ When I create 3 widgets
+ Then I should have 5 widgets
+
+ Scenario:
+ Then I should have 2 widgets
+ """
+ And I write to "features/step_definitions/widget_steps.rb" with:
+ """
+ Given /created? (\d) widgets/ do |num|
+ num.to_i.times { Widget.create! }
+ end
+
+ Then /should have (\d) widgets/ do |num|
+ Widget.count.should == num.to_i
+ end
+ """
+ When I run the cukes
+ Then it should pass with:
+ """
+ 2 scenarios (2 passed)
+ 5 steps (5 passed)
+ """
View
9 features/step_definitions/cucumber_rails_steps.rb
@@ -64,3 +64,12 @@ def fixture(path)
overwrite_file('features/support/env.rb', "require 'cucumber/rails'\n")
create_web_steps
end
+
+Given /^I have a "([^"]*)" ActiveRecord model object$/ do |name|
+ run_simple("bundle exec rails g model #{name}")
+ run_simple("bundle exec rake db:migrate RAILS_ENV=test")
+end
+
+When /^I run the cukes$/ do
+ run_simple('bundle exec cucumber')
+end
View
27 lib/cucumber/rails/database.rb
@@ -2,29 +2,32 @@ module Cucumber
module Rails
module Database
class << self
+
def javascript_strategy=(strategy)
strategy_type = map[strategy] || raise("The strategy '#{strategy}' is not understood. Please use one of #{map.keys.join(',')}")
@strategy = strategy_type.new
end
-
+
def before_js
@strategy.before_js
end
-
+
def before_non_js
@strategy.before_non_js
end
+
private
-
+
def map
- {
+ {
:truncation => TruncationStrategy,
:shared_connection => SharedConnectionStrategy,
:transaction => SharedConnectionStrategy
}
end
+
end
-
+
class SharedConnectionStrategy
def before_js
# Forces all threads to share a connection on a per-model basis,
@@ -35,7 +38,7 @@ def before_js
model.shared_connection = model.connection
end
end
-
+
def before_non_js
# Do not use a shared connection unless we're in a @javascript scenario
ActiveRecord::Base.shared_connection = nil
@@ -44,19 +47,21 @@ def before_non_js
end
end
end
-
+
class TruncationStrategy
def before_js
- @original_strategy = DatabaseCleaner.strategy
+ @original_strategy = DatabaseCleaner.connections.first.strategy # that feels like a nasty hack
DatabaseCleaner.strategy = :truncation
end
-
+
def before_non_js
+ return unless @original_strategy
DatabaseCleaner.strategy = @original_strategy
+ @original_strategy = nil
end
end
-
+
self.javascript_strategy = :transaction
end
end
-end
+end

0 comments on commit 9b03436

Please sign in to comment.