I have something like the following: def my_fun my_fun2 do raise Error end end I know that I can verify that the method receives my_fun2. How can I mock/stub out the example to verify that it calls raise Error? Scott
nicholas a. evans
2007-Apr-17 17:03 UTC
[rspec-users] Verifying that a block calls a method
On 4/17/07, Scott Taylor <mailing_lists at railsnewbie.com> wrote:> def my_fun > my_fun2 do > raise Error > end > end > > I know that I can verify that the method receives my_fun2. How can I > mock/stub out the example to verify that it calls raise Error?Have you tried "should raise_error(...)"? lambda { my_fun }.should raise_error http://rspec.rubyforge.org/rdoc/classes/Spec/Matchers.html#M000290 -- Nick
On Apr 17, 2007, at 1:03 PM, nicholas a. evans wrote:> On 4/17/07, Scott Taylor <mailing_lists at railsnewbie.com> wrote: >> def my_fun >> my_fun2 do >> raise Error >> end >> end >> >> I know that I can verify that the method receives my_fun2. How can I >> mock/stub out the example to verify that it calls raise Error? > > Have you tried "should raise_error(...)"? > > lambda { my_fun }.should raise_errorSure, I have - the problem is that the method my_fun2 only raises error under certain conditions - if for instance, Control-C is pressed. It is quite different from a block like the following, in which the method call is guaranteed to execute: def my_fun3 [1,2,3].each { |x| puts x } end Then a spec like this should work just fine: describe "my_fun3" do it "should call puts on the first element" do obj.should_receive(:puts).with(1) obj.my_fun3 end end The example shows that the second method being called (puts), should be mocked (because you don''t actually want to dump the numbers 1-3 to the terminal during your specs. How do you mock out my_fun3? The only clear advise I''ve gotten is to pass my_fun3 a stream, and let the default parameter be a STDOUT stream, like so: def my_fun3(stream = STDOUT) [1,2,3].each { |x| stream.puts x } end describe "my_fun3" do setup do @io = mock(io) @io.stub!(:puts).and_return @io end it "should call puts on the first element" do @io.should_receive(:puts).with(1) obj.my_fun3 end end Every time you use this method in production, you just leave off the parameter, and it defaults to calling puts (as normal). The stream parameter is only used during testing. This is a little disturbing, because now you have to change the source to fit your tests for code that will never be used in production. Luckily it''s not a big change, so it''s probably not a big deal. The specific example that I''m dealing with raises an error *only* when pressing ^C. def add_sigint_handler trap("INT") { raise Interrupt } end Raise only gets called if ^C (or an INT signal) is received. So how would I spec that method? The one thought that I had was that I should be able to pass the block to the method as the last parameter. Unfortunately, this doesn''t seem to work. Any other thoughts? Thanks, Scott Taylor> > http://rspec.rubyforge.org/rdoc/classes/Spec/Matchers.html#M000290 > > -- > Nick > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users
On 4/17/07, Scott Taylor <mailing_lists at railsnewbie.com> wrote:> The only clear advise I''ve gotten is to > pass my_fun3 a stream, and let the default parameter be a STDOUT > stream, like so: > > def my_fun3(stream = STDOUT) > [1,2,3].each { |x| stream.puts x } > end > > > Every time you use this method in production, you just leave off the > parameter, and it defaults to calling puts (as normal). The stream > parameter is only used during testing. This is a little disturbing, > because now you have to change the source to fit your tests for code > that will never be used in production.This is a very, very common approach in static languages - often used to break dependencies found in legacy (read: un-tested) code. Designing for testability is a tricky thing, for exactly the reason you describe. You end up designing things differently than you would if you didn''t care about testing. The trick is finding the boundaries that you''re comfortable with. I''m not comfortable with methods that are only used in tests. Parameterized methods or constructors don''t bug me at all. They serve the tests, and impose low risk of misuse. That risk is lowered, in my opinion, if you use a constructor parameterize the constructor rather than the method. class MyClass def initialize(io=STDOUT) @io = io end def my_fun3 [1,2,3].each { |x| @io.puts x } end end Hope that helps. David
On Apr 17, 2007, at 10:43 PM, David Chelimsky wrote:> On 4/17/07, Scott Taylor <mailing_lists at railsnewbie.com> wrote: >> The only clear advise I''ve gotten is to >> pass my_fun3 a stream, and let the default parameter be a STDOUT >> stream, like so: >> >> def my_fun3(stream = STDOUT) >> [1,2,3].each { |x| stream.puts x } >> end >> >> >> Every time you use this method in production, you just leave off the >> parameter, and it defaults to calling puts (as normal). The stream >> parameter is only used during testing. This is a little disturbing, >> because now you have to change the source to fit your tests for code >> that will never be used in production. > > This is a very, very common approach in static languages - often used > to break dependencies found in legacy (read: un-tested) code. > > Designing for testability is a tricky thing, for exactly the reason > you describe. You end up designing things differently than you would > if you didn''t care about testing. The trick is finding the boundaries > that you''re comfortable with. I''m not comfortable with methods that > are only used in tests. Parameterized methods or constructors don''t > bug me at all. They serve the tests, and impose low risk of misuse. > That risk is lowered, in my opinion, if you use a constructor > parameterize the constructor rather than the method. > > class MyClass > def initialize(io=STDOUT) > @io = io > end > def my_fun3 > [1,2,3].each { |x| @io.puts x } > end > end > > Hope that helps.Well it seems to be good advise. Unfortunately, it doesn''t help me with the Kernel#trap problem, though, which is what the email was originally about. Scott> > David > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users
On 4/18/07, David Chelimsky <dchelimsky at gmail.com> wrote:> On 4/17/07, Scott Taylor <mailing_lists at railsnewbie.com> wrote: > > The only clear advise I''ve gotten is to > > pass my_fun3 a stream, and let the default parameter be a STDOUT > > stream, like so: > > > > def my_fun3(stream = STDOUT) > > [1,2,3].each { |x| stream.puts x } > > end > > > > > > Every time you use this method in production, you just leave off the > > parameter, and it defaults to calling puts (as normal). The stream > > parameter is only used during testing. This is a little disturbing, > > because now you have to change the source to fit your tests for code > > that will never be used in production. > > This is a very, very common approach in static languages - often used > to break dependencies found in legacy (read: un-tested) code. > > Designing for testability is a tricky thing, for exactly the reason > you describe. You end up designing things differently than you would > if you didn''t care about testing. The trick is finding the boundaries > that you''re comfortable with. I''m not comfortable with methods that > are only used in tests. Parameterized methods or constructors don''t > bug me at all. They serve the tests, and impose low risk of misuse. > That risk is lowered, in my opinion, if you use a constructor > parameterize the constructor rather than the method. >A.k.a Constructor Dependency Injection (look it up) - a common principle to aid decoupling. Very common in Java land combined with mocks. Useful in Ruby land too.> class MyClass > def initialize(io=STDOUT) > @io = io > end > def my_fun3 > [1,2,3].each { |x| @io.puts x } > end > end > > Hope that helps. > > David > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >
On 4/18/07, Scott Taylor <mailing_lists at railsnewbie.com> wrote:> > On Apr 17, 2007, at 10:43 PM, David Chelimsky wrote: > > > On 4/17/07, Scott Taylor <mailing_lists at railsnewbie.com> wrote: > >> The only clear advise I''ve gotten is to > >> pass my_fun3 a stream, and let the default parameter be a STDOUT > >> stream, like so: > >> > >> def my_fun3(stream = STDOUT) > >> [1,2,3].each { |x| stream.puts x } > >> end > >> > >> > >> Every time you use this method in production, you just leave off the > >> parameter, and it defaults to calling puts (as normal). The stream > >> parameter is only used during testing. This is a little disturbing, > >> because now you have to change the source to fit your tests for code > >> that will never be used in production. > > > > This is a very, very common approach in static languages - often used > > to break dependencies found in legacy (read: un-tested) code. > > > > Designing for testability is a tricky thing, for exactly the reason > > you describe. You end up designing things differently than you would > > if you didn''t care about testing. The trick is finding the boundaries > > that you''re comfortable with. I''m not comfortable with methods that > > are only used in tests. Parameterized methods or constructors don''t > > bug me at all. They serve the tests, and impose low risk of misuse. > > That risk is lowered, in my opinion, if you use a constructor > > parameterize the constructor rather than the method. > > > > class MyClass > > def initialize(io=STDOUT) > > @io = io > > end > > def my_fun3 > > [1,2,3].each { |x| @io.puts x } > > end > > end > > > > Hope that helps. > > Well it seems to be good advise. Unfortunately, it doesn''t help me > with the Kernel#trap problem, though, which is what the email was > originally about. >If you want a concise answer, ask a short and concise question ;-) Try this: class Mooky def add_sigint_handler(trapper=Kernel) trapper.trap("INT") { raise Interrupt } end end describe Mooky do it ''should install an INT trap that raises an Interrupted'' do mooky = Mooky.new trapper = mock(''Kernel'') trapper.should_receive(:trap).with("INT").and_yield lambda do mooky.add_sigint_handler(trapper) end.should raise_error(Interrupt) end end Any other suggestions? Aslak> Scott > > > > > > > David > > _______________________________________________ > > rspec-users mailing list > > rspec-users at rubyforge.org > > http://rubyforge.org/mailman/listinfo/rspec-users > > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >
On 4/18/07, aslak hellesoy <aslak.hellesoy at gmail.com> wrote:> On 4/18/07, Scott Taylor <mailing_lists at railsnewbie.com> wrote: > > > > On Apr 17, 2007, at 10:43 PM, David Chelimsky wrote: > > > > > On 4/17/07, Scott Taylor <mailing_lists at railsnewbie.com> wrote: > > >> The only clear advise I''ve gotten is to > > >> pass my_fun3 a stream, and let the default parameter be a STDOUT > > >> stream, like so: > > >> > > >> def my_fun3(stream = STDOUT) > > >> [1,2,3].each { |x| stream.puts x } > > >> end > > >> > > >> > > >> Every time you use this method in production, you just leave off the > > >> parameter, and it defaults to calling puts (as normal). The stream > > >> parameter is only used during testing. This is a little disturbing, > > >> because now you have to change the source to fit your tests for code > > >> that will never be used in production. > > > > > > This is a very, very common approach in static languages - often used > > > to break dependencies found in legacy (read: un-tested) code. > > > > > > Designing for testability is a tricky thing, for exactly the reason > > > you describe. You end up designing things differently than you would > > > if you didn''t care about testing. The trick is finding the boundaries > > > that you''re comfortable with. I''m not comfortable with methods that > > > are only used in tests. Parameterized methods or constructors don''t > > > bug me at all. They serve the tests, and impose low risk of misuse. > > > That risk is lowered, in my opinion, if you use a constructor > > > parameterize the constructor rather than the method. > > > > > > class MyClass > > > def initialize(io=STDOUT) > > > @io = io > > > end > > > def my_fun3 > > > [1,2,3].each { |x| @io.puts x } > > > end > > > end > > > > > > Hope that helps. > > > > Well it seems to be good advise. Unfortunately, it doesn''t help me > > with the Kernel#trap problem, though, which is what the email was > > originally about. > > > > If you want a concise answer, ask a short and concise question ;-) Try this: > > class Mooky > def add_sigint_handler(trapper=Kernel) > trapper.trap("INT") { raise Interrupt } > end > end > > describe Mooky do > it ''should install an INT trap that raises an Interrupted'' do > mooky = Mooky.new > trapper = mock(''Kernel'') > trapper.should_receive(:trap).with("INT").and_yield > lambda do > mooky.add_sigint_handler(trapper) > end.should raise_error(Interrupt) > end > end > > Any other suggestions?It would be nice to actually send the signal. This, for example, works in isolation: require ''rubygems'' require ''spec'' class Trapper def add_sigint_handler trap("INT") { raise "trapped" } end end describe "interrupt" do it "should provide trap for INT" do lambda do Trapper.new.add_sigint_handler Process.kill("INT", 0) end.should raise_error(RuntimeError, "trapped") end end The problem is that RSpec handles Interrupt in its own way, and with this you''ve now changed the way rspec is going to behave when it receives Interrupt so you''re likely to get some unexpected results. David> > Aslak > > > Scott > > > > > > > > > > > > David > > > _______________________________________________ > > > rspec-users mailing list > > > rspec-users at rubyforge.org > > > http://rubyforge.org/mailman/listinfo/rspec-users > > > > _______________________________________________ > > rspec-users mailing list > > rspec-users at rubyforge.org > > http://rubyforge.org/mailman/listinfo/rspec-users > > > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >
>> >> Well it seems to be good advise. Unfortunately, it doesn''t help me >> with the Kernel#trap problem, though, which is what the email was >> originally about. >> > > If you want a concise answer, ask a short and concise question ;-) > Try this: > > class Mooky > def add_sigint_handler(trapper=Kernel) > trapper.trap("INT") { raise Interrupt } > end > end > > describe Mooky do > it ''should install an INT trap that raises an Interrupted'' do > mooky = Mooky.new > trapper = mock(''Kernel'') > trapper.should_receive(:trap).with("INT").and_yield > lambda do > mooky.add_sigint_handler(trapper) > end.should raise_error(Interrupt) > end > end > > Any other suggestions?Ah - I hadn''t noticed the "and_yield" method - I was trying to just pass the proc to the "with" method. That really clears things up. Thanks, Aslak. Scott> > Aslak > >> Scott >> >> >> >>> >>> David >>> _______________________________________________ >>> rspec-users mailing list >>> rspec-users at rubyforge.org >>> http://rubyforge.org/mailman/listinfo/rspec-users >> >> _______________________________________________ >> rspec-users mailing list >> rspec-users at rubyforge.org >> http://rubyforge.org/mailman/listinfo/rspec-users >> > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users