In my unit tests I sometimes mock Rails classes to avoid a complicated test setup. For instance, by redefining ActiveRecord::Base#find I can run tests without a database being involved. Tests like that work nicely by themselves when run by (plain) ruby or testrb. Trouble strikes when I try to run them from a rake task in conjunction with other tests. Most tests, of course, expect Rails classes to work as usual. Thus, what I need is a way to restrict my changes to the tests in one file. Well, it''s not possible to actually restrict it, but it is possible to revert it when it is no longer needed. That''s what the combo of setup and teardown can be used for. See below for a demonstration of the technique. Michael ----8><----8><----8><----8><----8><----8><----8>< $:.unshift File.join(File.dirname(__FILE__)) require ''test_helper'' require ''active_support'' require ''boilerplate/enumerations'' class ActiveRecord::Base cattr_accessor :find_result, :expect_find def self.mock_find(*args) fail "unexpected call to find" unless @@expect_find > 0 @@expect_find -= 1 find_result end end class EnumerationsTest < Test::Unit::TestCase DummyEnum = Struct.new(:name, :id) def setup ActiveRecord::Base.class_eval <<-END class <<self alias_method :original_find, :find alias_method :find, :mock_find end END expect_find(0) BoilerPlate::Enumerations::define_enums( ''SimpleEnum'' ) SimpleEnum.reset end def teardown ActiveRecord::Base.class_eval <<-END class <<self alias_method :find, :original_find end END end def expect_find(count = 1) ActiveRecord::Base::expect_find = count end def set_find_result(result) ActiveRecord::Base::find_result = result.dup end def test_all expect_find set_find_result [1, 2, 3] assert_equal( [1, 2, 3], SimpleEnum.all) end def test_all_should_cache expect_find set_find_result [1, 2, 3] assert_equal( [1, 2, 3], SimpleEnum.all) # no further call to find assert_equal( [1, 2, 3], SimpleEnum.all) end ### snipped end -- Michael Schuerig Nothing is as brilliantly adaptive mailto:michael-q5aiKMLteq4b1SvskN2V4Q@public.gmane.org as selective stupidity. http://www.schuerig.de/michael/ --A.O. Rorty, The Deceptive Self
Thanks Michael, that''s immensely handy. It feels like there aren''t too many people interested in unit testing in this way on this list. Perhaps those of us who are doing a fairly ''kent beck'' style TDD with mock objects and no database should get together for an online chat about techniques some time. Craig
We''re attempting TDD on Rails for our XP codefests in Columbia MD. ( http://agilemaryland.org/moin/CodeProject_2fMeetingSchedule)<http://agilemaryland.org/moin/CodeProject_2fMeetingSchedule>So we''re very interested in the challenges and solutions in doing this. One thing that has been bothering me is running generators or doing DB schema changes before writting a unit test. I''d like to see a unit test fail, run the generator, see unit test pass. Any advice on this? On 8/22/05, Craig Ambrose <craig-sZi9aYDroxztFRKb8DbDFhCuuivNXqWP@public.gmane.org> wrote:> > > Thanks Michael, that''s immensely handy. It feels like there aren''t too > many people interested in unit testing in this way on this list. Perhaps > those of us who are doing a fairly ''kent beck'' style TDD with mock > objects and no database should get together for an online chat about > techniques some time. > > Craig > > > > _______________________________________________ > Rails mailing list > Rails-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org > http://lists.rubyonrails.org/mailman/listinfo/rails >-- It is the duty of a patriot to protect his country from its government" -Thomas Paine Blog: http://superwombat.blogspot.com Bookmarks: http://del.icio.us/jeffwaltzer _______________________________________________ Rails mailing list Rails-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org http://lists.rubyonrails.org/mailman/listinfo/rails
I really like not having to use mockobjects but my tests are getting out of hand as far as run time speed is concerned, almost to the point where I avoid running them because I don''t get that instant feedback when I would use mockobjects in javaland, my rails tests are now taking about 40+ seconds to run on my main devel platform a 1.5ghz powerbook anyhow, how far do you plan on taking this? would be nice if I could just mock out all of AR (blindly assuming that AR/DB is the bulk of the speed hit) On 8/22/05, Michael Schuerig <michael-q5aiKMLteq4b1SvskN2V4Q@public.gmane.org> wrote:> > > In my unit tests I sometimes mock Rails classes to avoid a complicated > test setup. For instance, by redefining ActiveRecord::Base#find I can > run tests without a database being involved. > > Tests like that work nicely by themselves when run by (plain) ruby or > testrb. Trouble strikes when I try to run them from a rake task in > conjunction with other tests. Most tests, of course, expect Rails > classes to work as usual. Thus, what I need is a way to restrict my > changes to the tests in one file. Well, it''s not possible to actually > restrict it, but it is possible to revert it when it is no longer > needed. That''s what the combo of setup and teardown can be used for. > See below for a demonstration of the technique. > > Michael > > > ----8><----8><----8><----8><----8><----8><----8>< > > $:.unshift File.join(File.dirname(__FILE__)) > > require ''test_helper'' > require ''active_support'' > require ''boilerplate/enumerations'' > > > class ActiveRecord::Base > cattr_accessor :find_result, :expect_find > def self.mock_find(*args) > fail "unexpected call to find" unless @@expect_find > 0 > @@expect_find -= 1 > find_result > end > end > > class EnumerationsTest < Test::Unit::TestCase > > DummyEnum = Struct.new(:name, :id) > > def setup > ActiveRecord::Base.class_eval <<-END > class <<self > alias_method :original_find, :find > alias_method :find, :mock_find > end > END > > expect_find(0) > BoilerPlate::Enumerations::define_enums( > ''SimpleEnum'' > ) > SimpleEnum.reset > end > > def teardown > ActiveRecord::Base.class_eval <<-END > class <<self > alias_method :find, :original_find > end > END > end > > def expect_find(count = 1) > ActiveRecord::Base::expect_find = count > end > > def set_find_result(result) > ActiveRecord::Base::find_result = result.dup > end > > > def test_all > expect_find > set_find_result [1, 2, 3] > assert_equal( > [1, 2, 3], > SimpleEnum.all) > end > > def test_all_should_cache > expect_find > set_find_result [1, 2, 3] > assert_equal( > [1, 2, 3], > SimpleEnum.all) > # no further call to find > assert_equal( > [1, 2, 3], > SimpleEnum.all) > end > > ### snipped > > end > > -- > Michael Schuerig Nothing is as brilliantly adaptive > mailto:michael-q5aiKMLteq4b1SvskN2V4Q@public.gmane.org as selective stupidity. > http://www.schuerig.de/michael/ --A.O. Rorty, The Deceptive Self > _______________________________________________ > Rails mailing list > Rails-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org > http://lists.rubyonrails.org/mailman/listinfo/rails >_______________________________________________ Rails mailing list Rails-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org http://lists.rubyonrails.org/mailman/listinfo/rails
Sounds interesting Jeff. Just to get pedantic, it doesn''t really sound like a unit test if it''s testing the effect of generators on the code and database. More like an integration or acceptance test. However you could no doubt do that with Ruby::Test::Unit, but you''d probably need to do some sort of scripting over the top to make it happen. If you get specific about your example, I''d be happy to give you my thoughts on how to do it (such as they are). Craig Jeff Waltzer wrote:> We''re attempting TDD on Rails for our XP codefests in Columbia MD. > (http://agilemaryland.org/moin/CodeProject_2fMeetingSchedule) > <http://agilemaryland.org/moin/CodeProject_2fMeetingSchedule> So we''re > very interested in the challenges and solutions in doing this. One > thing that has been bothering me is running generators or doing DB > schema changes before writting a unit test. I''d like to see a unit > test fail, run the generator, see unit test pass. Any advice on this?
Hi Francisco, Further to Michael''s orginal post on this, I''ve added some addtional thoughts (and modified code) here: http://groups.yahoo.com/group/xp_on_rails/message/1 regards, Craig Francisco Hernandez wrote:> I really like not having to use mockobjects but my tests are getting > out of hand as far as run time speed is concerned, almost to the point > where I avoid running them because I don''t get that instant feedback > when I would use mockobjects in javaland, my rails tests are now > taking about 40+ seconds to run on my main devel platform a 1.5ghz > powerbook > > anyhow, how far do you plan on taking this? would be nice if I could > just mock out all of AR (blindly assuming that AR/DB is the bulk of > the speed hit)
On Monday 05 September 2005 00:18, Francisco Hernandez wrote:> anyhow, how far do you plan on taking this? would be nice if I could > just mock out all of AR (blindly assuming that AR/DB is the bulk of > the speed hit)I don''t plan anything, I just mock things up as I go. Michael -- Michael Schuerig Life is what happens mailto:michael-q5aiKMLteq4b1SvskN2V4Q@public.gmane.org While you''re making plans http://www.schuerig.de/michael/ --Kevin Gilbert, A Long Day''s Life