|
138 | 138 | # This is however a feature to be used with some caution. The point of fixtures are that they're stable units of predictable
|
139 | 139 | # sample data. If you feel that you need to inject dynamic values, then perhaps you should reexamine whether your application
|
140 | 140 | # is properly testable. Hence, dynamic values in fixtures are to be considered a code smell.
|
| 141 | +# |
| 142 | +# = Transactional fixtures |
| 143 | +# |
| 144 | +# TestCases can use begin+rollback to isolate their changes to the database instead of having to delete+insert for every test case. |
| 145 | +# They can also turn off auto-instantiation of fixture data since the feature is costly and often unused. |
| 146 | +# |
| 147 | +# class FooTest < Test::Unit::TestCase |
| 148 | +# self.use_transactional_fixtures = true |
| 149 | +# self.use_instantiated_fixtures = false |
| 150 | +# |
| 151 | +# fixtures :foos |
| 152 | +# |
| 153 | +# def test_godzilla |
| 154 | +# assert !Foo.find_all.emtpy? |
| 155 | +# Foo.destroy_all |
| 156 | +# assert Foo.find_all.emtpy? |
| 157 | +# end |
| 158 | +# |
| 159 | +# def test_godzilla_aftermath |
| 160 | +# assert !Foo.find_all.emtpy? |
| 161 | +# end |
| 162 | +# end |
| 163 | +# |
| 164 | +# If you preload your test database with all fixture data (probably in the Rakefile task) and use transactional fixtures, |
| 165 | +# then you may omit all fixtures declarations in your test cases since all the data's already there and every case rolls back its changes. |
| 166 | +# |
| 167 | +# When *not* to use transactional fixtures: |
| 168 | +# 1. You're testing whether a transaction works correctly. Nested transactions don't commit until all parent transactions commit, |
| 169 | +# particularly, the fixtures transaction which is begun in setup and rolled back in teardown. Thus, you won't be able to verify |
| 170 | +# the results of your transaction until Active Record supports nested transactions or savepoints (in progress.) |
| 171 | +# 2. Your database does not support transactions. Every Active Record database supports transactions except MySQL MyISAM. |
| 172 | +# Use InnoDB, MaxDB, or NDB instead. |
141 | 173 | class Fixtures < Hash
|
142 | 174 | DEFAULT_FILTER_RE = /\.ya?ml$/
|
143 | 175 |
|
144 |
| - def self.instantiate_fixtures(object, fixtures_directory, *table_names) |
145 |
| - [ create_fixtures(fixtures_directory, *table_names) ].flatten.each_with_index do |fixtures, idx| |
146 |
| - object.instance_variable_set "@#{table_names[idx]}", fixtures |
147 |
| - fixtures.each do |name, fixture| |
148 |
| - if model = fixture.find |
149 |
| - object.instance_variable_set "@#{name}", model |
150 |
| - end |
| 176 | + def self.instantiate_fixtures(object, table_name, fixtures) |
| 177 | + object.instance_variable_set "@#{table_name}", fixtures |
| 178 | + fixtures.each do |name, fixture| |
| 179 | + if model = fixture.find |
| 180 | + object.instance_variable_set "@#{name}", model |
151 | 181 | end
|
152 | 182 | end
|
153 | 183 | end
|
@@ -322,51 +352,100 @@ def read_fixture_file(fixture_file_path)
|
322 | 352 | end
|
323 | 353 | end
|
324 | 354 |
|
325 |
| -module Test#:nodoc: |
326 |
| - module Unit#:nodoc: |
| 355 | +module Test #:nodoc: |
| 356 | + module Unit #:nodoc: |
327 | 357 | class TestCase #:nodoc:
|
328 | 358 | include ClassInheritableAttributes
|
329 | 359 |
|
330 | 360 | cattr_accessor :fixture_path
|
331 |
| - cattr_accessor :fixture_table_names |
| 361 | + class_inheritable_accessor :fixture_table_names |
| 362 | + class_inheritable_accessor :use_transactional_fixtures |
| 363 | + class_inheritable_accessor :use_instantiated_fixtures |
| 364 | + |
| 365 | + self.fixture_table_names = [] |
| 366 | + self.use_transactional_fixtures = false |
| 367 | + self.use_instantiated_fixtures = true |
332 | 368 |
|
333 | 369 | def self.fixtures(*table_names)
|
334 |
| - require_fixture_classes(table_names) |
335 |
| - write_inheritable_attribute("fixture_table_names", table_names) |
| 370 | + self.fixture_table_names = table_names.flatten |
| 371 | + require_fixture_classes |
336 | 372 | end
|
337 | 373 |
|
338 |
| - def self.require_fixture_classes(table_names) |
339 |
| - table_names.each do |table_name| |
| 374 | + def self.require_fixture_classes |
| 375 | + fixture_table_names.each do |table_name| |
340 | 376 | begin
|
341 |
| - require(Inflector.singularize(table_name.to_s)) |
| 377 | + require Inflector.singularize(table_name.to_s) |
342 | 378 | rescue LoadError
|
343 |
| - # Let's hope the developer is included it himself |
| 379 | + # Let's hope the developer has included it himself |
344 | 380 | end
|
345 | 381 | end
|
346 | 382 | end
|
347 | 383 |
|
348 |
| - def setup |
349 |
| - instantiate_fixtures(*fixture_table_names) if fixture_table_names |
| 384 | + def setup_with_fixtures |
| 385 | + # Load fixtures once and begin transaction. |
| 386 | + if use_transactional_fixtures |
| 387 | + load_fixtures unless @already_loaded_fixtures |
| 388 | + @already_loaded_fixtures = true |
| 389 | + ActiveRecord::Base.lock_mutex |
| 390 | + ActiveRecord::Base.connection.begin_db_transaction |
| 391 | + |
| 392 | + # Load fixtures for every test. |
| 393 | + else |
| 394 | + load_fixtures |
| 395 | + end |
| 396 | + |
| 397 | + # Instantiate fixtures for every test if requested. |
| 398 | + instantiate_fixtures if use_instantiated_fixtures |
| 399 | + end |
| 400 | + |
| 401 | + alias_method :setup, :setup_with_fixtures |
| 402 | + |
| 403 | + def teardown_with_fixtures |
| 404 | + # Rollback changes. |
| 405 | + if use_transactional_fixtures |
| 406 | + ActiveRecord::Base.connection.rollback_db_transaction |
| 407 | + ActiveRecord::Base.unlock_mutex |
| 408 | + end |
350 | 409 | end
|
351 | 410 |
|
352 |
| - def self.method_added(method_symbol) |
353 |
| - if method_symbol == :setup && !method_defined?(:setup_without_fixtures) |
354 |
| - alias_method :setup_without_fixtures, :setup |
355 |
| - define_method(:setup) do |
356 |
| - instantiate_fixtures(*fixture_table_names) if fixture_table_names |
357 |
| - setup_without_fixtures |
| 411 | + alias_method :teardown, :teardown_with_fixtures |
| 412 | + |
| 413 | + def self.method_added(method) |
| 414 | + case method.to_s |
| 415 | + when 'setup' |
| 416 | + unless method_defined?(:setup_without_fixtures) |
| 417 | + alias_method :setup_without_fixtures, :setup |
| 418 | + define_method(:setup) do |
| 419 | + setup_with_fixtures |
| 420 | + setup_without_fixtures |
| 421 | + end |
| 422 | + end |
| 423 | + when 'teardown' |
| 424 | + unless method_defined?(:teardown_without_fixtures) |
| 425 | + alias_method :teardown_without_fixtures, :teardown |
| 426 | + define_method(:teardown) do |
| 427 | + teardown_without_fixtures |
| 428 | + teardown_with_fixtures |
| 429 | + end |
358 | 430 | end
|
359 | 431 | end
|
360 | 432 | end
|
361 | 433 |
|
362 | 434 | private
|
363 |
| - def instantiate_fixtures(*table_names) |
364 |
| - Fixtures.instantiate_fixtures(self, fixture_path, *table_names) |
| 435 | + def load_fixtures |
| 436 | + @loaded_fixtures = {} |
| 437 | + fixture_table_names.each do |table_name| |
| 438 | + @loaded_fixtures[table_name] = Fixtures.create_fixtures(fixture_path, table_name) |
| 439 | + end |
365 | 440 | end
|
366 | 441 |
|
367 |
| - def fixture_table_names |
368 |
| - self.class.read_inheritable_attribute("fixture_table_names") |
| 442 | + def instantiate_fixtures |
| 443 | + raise RuntimeError, 'Load fixtures before instantiating them.' if @loaded_fixtures.nil? |
| 444 | + @loaded_fixtures.each do |table_name, fixtures| |
| 445 | + Fixtures.instantiate_fixtures(self, table_name, fixtures) |
| 446 | + end |
369 | 447 | end
|
370 | 448 | end
|
| 449 | + |
371 | 450 | end
|
372 | 451 | end
|
0 commit comments