I''m trying to setup some specs (really just assertions) that verify some callbacks are executed in response to COM events. In the WIN32OLE_EVENT class you may subscribe to a COM event and have it delivered to you for processing. Syntax looks like: event_handler.on_event(''StartEvent'') do |*args| do_start_event args end The #do_start_event method is a member of the containing class. The only way I can confirm that it is being called is to set an expectation on the class under test. I''ve always noted that mocking/ stubbing the class under test is rather bad form. Regardless, I don''t know how to "mock" the delivery of an event anyway so this may all be moot. I searched the list archives for ''win32ole'' back to early 2007 and came up with 0 hits. Any suggestions on how to tackle this? cr
On 26 Aug 2009, at 19:57, Chuck Remes wrote:> I''m trying to setup some specs (really just assertions) that verify > some callbacks are executed in response to COM events. In the > WIN32OLE_EVENT class you may subscribe to a COM event and have it > delivered to you for processing. Syntax looks like: > > event_handler.on_event(''StartEvent'') do |*args| > do_start_event args > end > > The #do_start_event method is a member of the containing class. The > only way I can confirm that it is being called is to set an > expectation on the class under test. I''ve always noted that mocking/ > stubbing the class under test is rather bad form. Regardless, I > don''t know how to "mock" the delivery of an event anyway so this may > all be moot. > > I searched the list archives for ''win32ole'' back to early 2007 and > came up with 0 hits. Any suggestions on how to tackle this?This doesn''t really have much to do with the specific technology you''re dealing with, it''s an issue about separating the layers so you can introduce fakes where you need to in order to test your own code. Can you use dependency injection (or some other trick) to swap in a fake event_handler into the code you''ve quoted above? If so, you can then get your fake to simulate the behaviour of the event_handler and exercise the code you want to actually test. There are a couple of good books on these sort of techniques: http://xunitpatterns.com/ http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052 does that help? Matt
On Aug 27, 2009, at 10:12 AM, Matt Wynne wrote:> > On 26 Aug 2009, at 19:57, Chuck Remes wrote: > >> I''m trying to setup some specs (really just assertions) that verify >> some callbacks are executed in response to COM events. In the >> WIN32OLE_EVENT class you may subscribe to a COM event and have it >> delivered to you for processing. Syntax looks like: >> >> event_handler.on_event(''StartEvent'') do |*args| >> do_start_event args >> end >> >> The #do_start_event method is a member of the containing class. The >> only way I can confirm that it is being called is to set an >> expectation on the class under test. I''ve always noted that mocking/ >> stubbing the class under test is rather bad form. Regardless, I >> don''t know how to "mock" the delivery of an event anyway so this >> may all be moot. >> >> I searched the list archives for ''win32ole'' back to early 2007 and >> came up with 0 hits. Any suggestions on how to tackle this? > > This doesn''t really have much to do with the specific technology > you''re dealing with, it''s an issue about separating the layers so > you can introduce fakes where you need to in order to test your own > code. > > Can you use dependency injection (or some other trick) to swap in a > fake event_handler into the code you''ve quoted above? If so, you can > then get your fake to simulate the behaviour of the event_handler > and exercise the code you want to actually test. > > There are a couple of good books on these sort of techniques: > http://xunitpatterns.com/ > http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052 > > does that help?It does somewhat. I will definitely be looking at that XUnit patterns book. To answer an earlier question, I *can* pass in a mock for event_handler. However, I fail to see how I can use that in the example above to test that the statements in the block are executed. def bar event_handler event_handler.on_event(''StartEvent'') do |baz, qux| @obj1.method1 baz, qux end end Let''s assume that method1 and method2 are acting upon other objects that I can also mock via DI. How can I generate the ''StartEvent'' so that the block above is executed? BTW, the layers are as separated as they are going to get. In the WIN32OLE_EVENT class you *must* pass a block as shown above. I don''t know how to get that block to execute in my specs without actually connecting to the COM service and getting it to generate that ''StartEvent''. Do you know of another way? it "should execute the block upon receipt of event ''StartEvent''" do handler = mock("event handler") handler.should_receive(:on_event).with(''StartEvent'') # necessary? obj1_mock = mock("obj1") obj1.should_receive(:method1) foo = Foo.new foo.obj1 = obj1 foo.bar # callback has been set; how do I generate the ''StartEvent'' # to run that block? foo.??? end Do you see my problem more clearly with this example? Or have I obfuscated it even more... :) cr
On 27 Aug 2009, at 17:02, Chuck Remes wrote:> > On Aug 27, 2009, at 10:12 AM, Matt Wynne wrote: > >> >> On 26 Aug 2009, at 19:57, Chuck Remes wrote: >> >>> I''m trying to setup some specs (really just assertions) that >>> verify some callbacks are executed in response to COM events. In >>> the WIN32OLE_EVENT class you may subscribe to a COM event and have >>> it delivered to you for processing. Syntax looks like: >>> >>> event_handler.on_event(''StartEvent'') do |*args| >>> do_start_event args >>> end >>> >>> The #do_start_event method is a member of the containing class. >>> The only way I can confirm that it is being called is to set an >>> expectation on the class under test. I''ve always noted that >>> mocking/stubbing the class under test is rather bad form. >>> Regardless, I don''t know how to "mock" the delivery of an event >>> anyway so this may all be moot. >>> >>> I searched the list archives for ''win32ole'' back to early 2007 and >>> came up with 0 hits. Any suggestions on how to tackle this? >> >> This doesn''t really have much to do with the specific technology >> you''re dealing with, it''s an issue about separating the layers so >> you can introduce fakes where you need to in order to test your own >> code. >> >> Can you use dependency injection (or some other trick) to swap in a >> fake event_handler into the code you''ve quoted above? If so, you >> can then get your fake to simulate the behaviour of the >> event_handler and exercise the code you want to actually test. >> >> There are a couple of good books on these sort of techniques: >> http://xunitpatterns.com/ >> http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052 >> >> does that help? > > It does somewhat. I will definitely be looking at that XUnit > patterns book. > > To answer an earlier question, I *can* pass in a mock for > event_handler. However, I fail to see how I can use that in the > example above to test that the statements in the block are executed. > > def bar event_handler > event_handler.on_event(''StartEvent'') do |baz, qux| > @obj1.method1 baz, qux > end > end > > Let''s assume that method1 and method2 are acting upon other objects > that I can also mock via DI. How can I generate the ''StartEvent'' so > that the block above is executed? BTW, the layers are as separated > as they are going to get. In the WIN32OLE_EVENT class you *must* > pass a block as shown above. I don''t know how to get that block to > execute in my specs without actually connecting to the COM service > and getting it to generate that ''StartEvent''. Do you know of another > way? > > it "should execute the block upon receipt of event ''StartEvent''" do > handler = mock("event handler") > handler.should_receive(:on_event).with(''StartEvent'') # necessary? > > obj1_mock = mock("obj1") > obj1.should_receive(:method1) > > foo = Foo.new > foo.obj1 = obj1 > > foo.bar > # callback has been set; how do I generate the ''StartEvent'' > # to run that block? > foo.??? > endSo I''m not sure if you can simulate a block being yeilded with RSpec''s mocks, it''s probably possible but I think you''d be simpler using a hand-made fake: class FakeEventHandler def initialize(*args_to_yield) @args_to_yield = *args_to_yield end def on_event(event_name) yield *@args_to_yield end end Something like that. Point being, a test-double (fake) doesn''t always have to be a mock object.
On Aug 27, 2009, at 12:22 PM, Matt Wynne wrote:> > On 27 Aug 2009, at 17:02, Chuck Remes wrote: >> >> Let''s assume that method1 and method2 are acting upon other objects >> that I can also mock via DI. How can I generate the ''StartEvent'' so >> that the block above is executed? BTW, the layers are as separated >> as they are going to get. In the WIN32OLE_EVENT class you *must* >> pass a block as shown above. I don''t know how to get that block to >> execute in my specs without actually connecting to the COM service >> and getting it to generate that ''StartEvent''. Do you know of >> another way? >> >> it "should execute the block upon receipt of event ''StartEvent''" do >> handler = mock("event handler") >> handler.should_receive(:on_event).with(''StartEvent'') # necessary? >> >> obj1_mock = mock("obj1") >> obj1.should_receive(:method1) >> >> foo = Foo.new >> foo.obj1 = obj1 >> >> foo.bar >> # callback has been set; how do I generate the ''StartEvent'' >> # to run that block? >> foo.??? >> end > > So I''m not sure if you can simulate a block being yeilded with > RSpec''s mocks, it''s probably possible but I think you''d be simpler > using a hand-made fake: > > class FakeEventHandler > def initialize(*args_to_yield) > @args_to_yield = *args_to_yield > end > > def on_event(event_name) > yield *@args_to_yield > end > end > > Something like that. > > Point being, a test-double (fake) doesn''t always have to be a mock > object.<slaps forehead> I often forget this. I have written fakes in the past using just this technique but it''s so rare that I had forgotten about it again. I usually remember things permanently after I have had to rediscover them 4 or 5 times. :) Thanks for your insight. This technique will cure my testing problem. cr