rsanheim-unit_record 0.9.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/CHANGELOG +34 -0
  2. data/LICENSE +20 -0
  3. data/README.markdown +137 -0
  4. data/Rakefile +94 -0
  5. data/lib/active_record/connection_adapters/unit_record_adapter.rb +97 -0
  6. data/lib/unit_record.rb +32 -0
  7. data/lib/unit_record/association_stubbing.rb +39 -0
  8. data/lib/unit_record/column_extension.rb +18 -0
  9. data/lib/unit_record/disconnected_active_record.rb +21 -0
  10. data/lib/unit_record/disconnected_fixtures.rb +10 -0
  11. data/lib/unit_record/disconnected_test_case.rb +13 -0
  12. data/test/active_record/connection_adapters/unit_record_adapter_test.rb +103 -0
  13. data/test/db/schema.rb +26 -0
  14. data/test/sample_spec.rb +44 -0
  15. data/test/test_helper.rb +73 -0
  16. data/test/unit_record/association_stubbing_test.rb +37 -0
  17. data/test/unit_record/column_cacher_test.rb +26 -0
  18. data/test/unit_record/column_extension_test.rb +33 -0
  19. data/test/unit_record/column_test.rb +40 -0
  20. data/test/unit_record/controller_test.rb +43 -0
  21. data/test/unit_record/disconnected_active_record_test.rb +52 -0
  22. data/test/unit_record/disconnected_fixtures_test.rb +7 -0
  23. data/test/unit_record/disconnected_test_case_test.rb +19 -0
  24. data/test/unit_record/unit_record_test.rb +14 -0
  25. data/vendor/dust-0.1.6/lib/array_extension.rb +5 -0
  26. data/vendor/dust-0.1.6/lib/definition_error.rb +20 -0
  27. data/vendor/dust-0.1.6/lib/dust.rb +8 -0
  28. data/vendor/dust-0.1.6/lib/nil_extension.rb +5 -0
  29. data/vendor/dust-0.1.6/lib/object_extension.rb +62 -0
  30. data/vendor/dust-0.1.6/lib/string_extension.rb +5 -0
  31. data/vendor/dust-0.1.6/lib/symbol_extension.rb +5 -0
  32. data/vendor/dust-0.1.6/lib/test_case_extension.rb +76 -0
  33. data/vendor/dust-0.1.6/rakefile.rb +50 -0
  34. data/vendor/dust-0.1.6/test/all_tests.rb +1 -0
  35. data/vendor/dust-0.1.6/test/failing_with_helper_unit_test.rb +16 -0
  36. data/vendor/dust-0.1.6/test/failing_with_setup_unit_test.rb +16 -0
  37. data/vendor/dust-0.1.6/test/functional_test.rb +12 -0
  38. data/vendor/dust-0.1.6/test/passing_unit_test.rb +11 -0
  39. data/vendor/dust-0.1.6/test/passing_with_helper_unit_test.rb +10 -0
  40. data/vendor/dust-0.1.6/test/passing_with_helpers_unit_test.rb +13 -0
  41. data/vendor/dust-0.1.6/test/passing_with_setup_unit_test.rb +10 -0
  42. data/vendor/dust-0.1.6/test/test_helper.rb +1 -0
  43. metadata +94 -0
data/CHANGELOG ADDED
@@ -0,0 +1,34 @@
1
+ *HEAD
2
+
3
+ * Add association stubbing with mocha
4
+
5
+ ActiveRecord::Base.disconnect! :stub_associations => true
6
+
7
+ * Add option to no-op instead of raise.
8
+
9
+ ActiveRecord::Base.disconnect! :strategy => :noop
10
+ or
11
+ ActiveRecord::Base.disconnect! :strategy => :raise
12
+
13
+ * Implemented as a connection adapter
14
+
15
+ *0.4.1* (December 10th, 2007)
16
+
17
+ * Stub caching for compatibility with ActionController::Caching::SqlCache in Rails 2.
18
+
19
+ *0.4.0* (December 9th, 2007)
20
+
21
+ * Rails 2.0 compatibility.
22
+
23
+ *0.3.0* (August 22nd, 2007)
24
+
25
+ * Works with models using non-conventional table names.
26
+
27
+ *0.2.0* (August 16th, 2007)
28
+
29
+ * Cache columns based on schema.rb
30
+
31
+ *0.1.0* (August 15th, 2007)
32
+
33
+ * Converted plugin into a gem.
34
+ * No longer depend on Rake task to dump and cache columns.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2007-2008 Dan Manges
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,137 @@
1
+ UnitRecord
2
+ ==========
3
+
4
+ Enables unit testing ActiveRecord classes without hitting the database.
5
+
6
+ Why?
7
+ ----
8
+
9
+ Rationale: [http://www.dcmanges.com/blog/rails-unit-record-test-without-the-database](http://www.dcmanges.com/blog/rails-unit-record-test-without-the-database)
10
+
11
+ One of the biggest benefits to disconnecting unit tests from the database is having a faster test suite. Here is the benchmark from one of my current projects:
12
+
13
+ Finished in 19.302702 seconds.
14
+ 4920 tests, 7878 assertions, 0 failures, 0 errors
15
+
16
+ 4 seconds per 1,000 tests is a good guideline.
17
+
18
+ Installation
19
+ ------------
20
+
21
+ gem install unit_record
22
+
23
+ Usage
24
+ -----
25
+
26
+ Restructuring the Rails Test Directory
27
+ --------------------------------------
28
+
29
+ The Rails test directory typically places testing for models under <tt>test/unit</tt> and tests for controllers under <tt>test/functional</tt>. However, we need to change the definition of unit and functional. Controllers can be unit tested (mocking out models and not rendering the view). Models can be functionally tested (hitting the database). Also, each type of test needs its own test\_helper. I recommend restructuring your test directory like this:
30
+
31
+ test
32
+ test_helper.rb
33
+ unit
34
+ unit_test_helper.rb
35
+ controllers
36
+ models
37
+ functional
38
+ functional_test_helper.rb
39
+ controllers
40
+ models
41
+
42
+ You should move existing functional tests into functional/controllers. You will also need to change the require line at the top of those tests to require the functional\_test\_helper.rb file instead of the test\_helper.rb file.
43
+
44
+ The <tt>functional_test_helper.rb</tt> file only needs to require <tt>test_helper.rb</tt>:
45
+
46
+ require File.dirname(__FILE__) + "/../test_helper"
47
+
48
+ For moving unit tests, you have a few options. I recommend moving them to unit/models and then disconnecting your unit tests from the database. Any tests that fail should then be modified to not hit the database or moved to functional/models.
49
+
50
+ Usage
51
+ -----
52
+
53
+ In the <tt>test/unit/unit\_test\_helper.rb</tt> file you created when restructuring your test directory, you should add these lines:
54
+
55
+ require File.dirname(__FILE__) + "/../test_helper"
56
+ require "unit_record"
57
+ ActiveRecord::Base.disconnect!
58
+
59
+ The <tt>disconnect!</tt> method will do everything necessary to run your unit tests without hitting the database.
60
+
61
+ Strategy
62
+ --------
63
+
64
+ There are two options for what should happen if you hit the database. You can either have UnitRecord raise an exception, or simply no-op. Raising an exception can help identify why a test is failing, but it also may be inconvenient to work around.
65
+
66
+ If you want to raise an exception:
67
+
68
+ ActiveRecord::Base.disconnect! :strategy => :raise
69
+
70
+ Person.find(:all)
71
+ #=> RuntimeError: ActiveRecord is disconnected; database access is unavailable in unit tests.
72
+
73
+ If you want to no-op:
74
+
75
+ ActiveRecord::Base.disconnect! :strategy => :noop
76
+
77
+ Person.find(:all)
78
+ #=> []
79
+
80
+ You can also change strategies within a block:
81
+
82
+ ActiveRecord::Base.connection.change_strategy(:raise) do
83
+ Person.find(:all)
84
+ #=> RuntimeError: ActiveRecord is disconnected; database access is unavailable in unit tests.
85
+ end
86
+
87
+ ActiveRecord::Base.connection.change_strategy(:noop) do
88
+ Person.find(:all)
89
+ #=> []
90
+ end
91
+
92
+ Association Stubbing
93
+ --------------------
94
+
95
+ One painful aspect of unit testing ActiveRecord classes is setting associations. Because Rails does type checking when setting an association, you'll receive an exception if you try to use a stub in place of the expected class.
96
+
97
+ Pet.new :owner => stub("person")
98
+ #=> ActiveRecord::AssociationTypeMismatch: Person(#16620740) expected, got Mocha::Mock(#11567340)
99
+
100
+ If you're using mocha, you can have UnitRecord stub associations. To enable association stubbing:
101
+
102
+ ActiveRecord::Base.disconnect! :stub_associations => true
103
+
104
+ The above example would no longer raise an exception. It would be the equivalent of:
105
+
106
+ pet = Pet.new
107
+ pet.stubs(:owner).returns(stub("person"))
108
+
109
+ Note that using this approach, the setter for the association will not work for that instance.
110
+
111
+ Development
112
+ -----------
113
+
114
+ Active development occurs on the [GitHub](http://github.com/dan-manges/unit-record). Changes are also pushed to the Rubyforge git repository.
115
+
116
+ For bugs/patches/etc please use the [Rubyforge tracker](http://rubyforge.org/tracker/?group_id=4239).
117
+
118
+ Continuous integration is provided by [RunCodeRun](http://runcoderun.com/dan-manges/unit-record).
119
+
120
+ Thanks
121
+ ------
122
+ Thanks to Jay Fields for the [original implementation](http://blog.jayfields.com/2007/03/rails-activerecord-unit-testing-part-ii.html).
123
+
124
+ Maintainer
125
+ ----------
126
+
127
+ [Dan Manges](http://www.dcmanges.com)
128
+
129
+ Contributors
130
+ ------------
131
+
132
+ * David Lowenfels
133
+ * Rob Sanheim
134
+
135
+ License
136
+ -------
137
+ Released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,94 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+
4
+ desc "Default: run tests"
5
+ task :default => %w[test:multi_verbose spec]
6
+
7
+ Rake::TestTask.new("test") do |t|
8
+ t.pattern = "test/**/*_test.rb"
9
+ t.verbose = true
10
+ end
11
+
12
+ begin
13
+ require "rcov/rcovtask"
14
+ desc "run tests with rcov"
15
+ Rcov::RcovTask.new do |t|
16
+ t.pattern = "test/**/*_test.rb"
17
+ t.rcov_opts << ["--no-html", "--exclude 'Library,#{Gem.path.join(',')}'"]
18
+ t.verbose = true
19
+ end
20
+ rescue LoadError
21
+ end
22
+
23
+ require "date"
24
+
25
+ gem_spec = Gem::Specification.new do |s|
26
+ s.name = "unit_record"
27
+ s.summary = "UnitRecord enables unit testing without hitting the database."
28
+ s.version = "0.9.0.1"
29
+ s.author = "Dan Manges"
30
+ s.description = "UnitRecord enables unit testing without hitting the database."
31
+ s.email = "daniel.manges@gmail.com"
32
+ s.homepage = "http://unit-test-ar.rubyforge.org"
33
+ s.rubyforge_project = "unit-test-ar"
34
+
35
+ s.has_rdoc = false
36
+
37
+ s.autorequire = "unit_record"
38
+ s.files = FileList['{lib,test,vendor}/**/*.rb', 'CHANGELOG', 'LICENSE', 'README.markdown', 'Rakefile'].to_a
39
+ end
40
+
41
+ task :gem => %w[test:multi] do
42
+ Gem::Builder.new(gem_spec).build
43
+ end
44
+
45
+ namespace :gemspec do
46
+ desc "generates unit-record.gemspec"
47
+ task :generate do
48
+ File.open("unit-record.gemspec", "w") do |f|
49
+ f.puts "# this file is generated by rake gemspec:generate for github"
50
+ f.write gem_spec.to_ruby
51
+ end
52
+ end
53
+ end
54
+
55
+ task :readme do
56
+ require "rubygems"; gem "BlueCloth"; require "BlueCloth"; require 'tmpdir'
57
+ file = "#{Dir.tmpdir}/readme.html"
58
+ File.open(file, "w") { |f| f.write BlueCloth.new(File.read("README.markdown")).to_html }
59
+ sh "open #{file}"
60
+ end
61
+
62
+ RAILS_VERSIONS = %w[1.2.6 2.0.2 2.1.0 2.1.1 2.2.2 2.3.1]
63
+
64
+ namespace :test do
65
+ desc "test with multiple versions of rails"
66
+ task :multi do
67
+ RAILS_VERSIONS.each do |rails_version|
68
+ puts "Testing with Rails #{rails_version}"
69
+ sh "RAILS_VERSION='#{rails_version}' rake test > /dev/null 2>&1"
70
+ end
71
+ end
72
+
73
+ task :multi_verbose do
74
+ (RAILS_VERSIONS - %w[2.2.2]).each do |rails_version|
75
+ task = defined?(Rcov) ? "rcov" : "test"
76
+ sh "RAILS_VERSION='#{rails_version}' rake #{task}"
77
+ end
78
+ end
79
+ end
80
+
81
+ begin
82
+ gem "rspec"
83
+ require "spec/rake/spectask"
84
+ Spec::Rake::SpecTask.new(:spec) do |t|
85
+ t.spec_files = %w[test/sample_spec.rb]
86
+ end
87
+ rescue LoadError
88
+ task :spec do
89
+ puts "== RSpec failed to load"
90
+ end
91
+ end
92
+
93
+ desc "pre-commit task"
94
+ task :pc => %w[test:multi spec gemspec:generate]
@@ -0,0 +1,97 @@
1
+ class ActiveRecord::ConnectionAdapters::UnitRecordAdapter < ::ActiveRecord::ConnectionAdapters::AbstractAdapter
2
+ EXCEPTION_MESSAGE = "ActiveRecord is disconnected; database access is unavailable in unit tests."
3
+
4
+ def initialize(config = {})
5
+ super
6
+ @strategy = config[:strategy] || :raise
7
+ @cached_columns = {"schema_info" => []}
8
+ end
9
+
10
+ def columns(table_name, name = nil)#:nodoc:
11
+ @cached_columns[table_name.to_s] ||
12
+ raise("Columns are not cached for '#{table_name}' - check schema.rb")
13
+ end
14
+
15
+ def create_table(table_name, options={})
16
+ table_definition = ActiveRecord::ConnectionAdapters::TableDefinition.new(self)
17
+ table_definition.primary_key(options[:primary_key] || "id") unless options[:id] == false
18
+ yield table_definition
19
+ @cached_columns[table_name.to_s] =
20
+ table_definition.columns.map do |c|
21
+ ActiveRecord::ConnectionAdapters::Column.new(c.name.to_s, c.default, c.sql_type, c.null)
22
+ end
23
+ end
24
+
25
+ def native_database_types
26
+ # Copied from the MysqlAdapter so ColumnDefinition#sql_type will work
27
+ {
28
+ :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
29
+ :string => { :name => "varchar", :limit => 255 },
30
+ :text => { :name => "text" },
31
+ :integer => { :name => "int", :limit => 11 },
32
+ :float => { :name => "float" },
33
+ :decimal => { :name => "decimal" },
34
+ :datetime => { :name => "datetime" },
35
+ :timestamp => { :name => "datetime" },
36
+ :time => { :name => "time" },
37
+ :date => { :name => "date" },
38
+ :binary => { :name => "blob" },
39
+ :boolean => { :name => "tinyint", :limit => 1 }
40
+ }
41
+ end
42
+
43
+ def change_strategy(new_strategy, &block)
44
+ unless [:noop, :raise].include?(new_strategy.to_sym)
45
+ raise ArgumentError, "#{new_strategy.inspect} is not a valid strategy - valid values are :noop and :raise"
46
+ end
47
+ begin
48
+ old_strategy = @strategy
49
+ @strategy = new_strategy.to_sym
50
+ yield
51
+ ensure
52
+ @strategy = old_strategy
53
+ end
54
+ end
55
+
56
+ def execute(sql, name = nil)
57
+ raise_or_noop
58
+ end
59
+
60
+ def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
61
+ raise_or_noop
62
+ end if Rails::VERSION::MAJOR == 1
63
+
64
+ def select_rows(sql, name = nil)
65
+ raise_or_noop []
66
+ end
67
+
68
+ def rename_table(table_name, new_name)
69
+ raise_or_noop
70
+ end
71
+
72
+ def change_column(table_name, column_name, type, options = {})
73
+ raise_or_noop
74
+ end
75
+
76
+ def change_column_default(table_name, column_name, default)
77
+ raise_or_noop
78
+ end
79
+
80
+ def rename_column(table_name, column_name, new_column_name)
81
+ raise_or_noop
82
+ end
83
+
84
+ def tables
85
+ @cached_columns.keys
86
+ end
87
+
88
+ protected
89
+
90
+ def raise_or_noop(noop_return_value = nil)
91
+ @strategy == :raise ? raise(EXCEPTION_MESSAGE) : noop_return_value
92
+ end
93
+
94
+ def select(sql, name = nil)
95
+ raise_or_noop []
96
+ end
97
+ end
@@ -0,0 +1,32 @@
1
+ module UnitRecord
2
+ def self.rails_version
3
+ Rails::VERSION::STRING
4
+ end
5
+
6
+ def self.base_rails_test_class
7
+ if rails_version >= "2.3.1"
8
+ ActiveSupport::TestCase
9
+ else
10
+ Test::Unit::TestCase
11
+ end
12
+ end
13
+ end
14
+ require "unit_record/association_stubbing"
15
+ require "unit_record/column_extension"
16
+ require "unit_record/disconnected_active_record"
17
+ require "unit_record/disconnected_test_case"
18
+ require "unit_record/disconnected_fixtures"
19
+ require "active_record/connection_adapters/unit_record_adapter"
20
+
21
+ require "active_record/fixtures"
22
+
23
+ ActiveRecord::ConnectionAdapters::Column.send :include, UnitRecord::ColumnExtension
24
+ ActiveRecord::Base.extend UnitRecord::DisconnectedActiveRecord
25
+ UnitRecord.base_rails_test_class.extend UnitRecord::DisconnectedTestCase
26
+ Fixtures.extend UnitRecord::DisconnectedFixtures
27
+
28
+ ActiveRecord::Base.class_eval do
29
+ def self.unit_record_connection(config)
30
+ ActiveRecord::ConnectionAdapters::UnitRecordAdapter.new(config)
31
+ end
32
+ end
@@ -0,0 +1,39 @@
1
+ module UnitRecord
2
+ module AssociationStubbing
3
+
4
+ private
5
+
6
+ def initialize_with_association_stubbing(attributes = {})
7
+ attributes ||= {}
8
+ associations = extract_associations attributes
9
+ initialize_without_association_stubbing attributes
10
+ stub_associations associations
11
+ end
12
+
13
+ protected
14
+
15
+ def extract_associations(attributes = {})
16
+ attributes.inject({}) do |associations,(attr,value)|
17
+ next associations unless self.class.reflections.keys.include? attr
18
+ unless value.is_a?(self.class.reflections[attr].klass)
19
+ associations[attr] = attributes.delete attr
20
+ end
21
+ associations
22
+ end
23
+ end
24
+
25
+ def stub_associations(associations = {})
26
+ associations.each do |attr,value|
27
+ self.stubs(attr).returns(value)
28
+ end
29
+ end
30
+
31
+ def self.included(klass)
32
+ klass.class_eval do
33
+ unless (instance_methods + private_instance_methods).include?("initialize_without_association_stubbing")
34
+ alias_method_chain :initialize, :association_stubbing
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end