Open sancarn opened 4 years ago
This is something I've been after for a while too. I don't know if C# allows you to hook onto arbitrary COM events - e.g. does IDispatch expose VBA Class events in a way that lets C# enumerate and hook into them, so you can set up some EventLogger(source As IDispatch)
. I know VBA doesn't have any easy way of enumerating Events a class exposes, let alone adding handlers to them at runtime. If possible in C#, then a class that logs event args and timestamps / calling order would probably be sufficient and a great addition to the RD testing suite.
If not then an alternative could be a feature that uses RD's existing knowledge of the users codebase and the events each of their classes exposes, and then creates a VBA handler manually and adds it to the project. Some parameterised Template class which takes in a base object, inspects its events in the RD parse tree, and stringly creates some handler code. I could suggest a template structure for this?
Either way I think it'd be so useful to have this feature, I use Events extensively for Asynchronous style programming and testing these without having to write custom listeners for events would be delightful
I see two possible solutions:
1) create a class module implementing the events with a WithEvents
and use it as a SUT, executing Verify
within the events of the class module, and having your test module call it.
2) Using type library API to create a connection point to the source interface representing the events. That requires a mock object, for which we need a mocking framework.
I could have sworn I had written about "testing that an event was raised" in "how to unit test VBA code", ...but I can't find any mention of it (might be I removed it at one point).
The way it was setup felt a bit clunky (that's probably why I ended editing it out), but basically when your SUT is an event provider then you can have a Private WithEvents SUT As Something
, and then some WasSomeEventRaised As Boolean
state flag that your SUT_SomeEvent
handler sets to True
(and that your @TestCleanUp
method sets back to False
), and then the test can Assert.IsTrue Test.WasSomeEventRaised
.
Now the problem is that RD test modules being standard modules, you cannot have a Private WithEvents SUT
variable in the test module, so you'd need to have some "test event handler" class that can handle the SUT events and expose the invoke state to the tests.
That's basically what @bclothier is suggesting as solution 1 =)
@retailcoder yes, and that's what I was suggesting as some stringly generated template class - RD could do all the heavy lifting of generating source code for the Boolean toggling, event handling test classes
Or indeed instead of true/false, why not count events / store them as entries in a collection which captures the state of the parameters too, alongside a timestamp to order them. I wanted to test a buffered list that raised an event every n
items for example, so Assert.AreEqual 2, Test.CountOfSomeEvent
would have been ideal
Is the typelib API approach (2) the proposed method to "listen" to VBA COM events directly within C# then? Just out of interest:)
@retailcoder Yes I had a suspission this was the current methodology.
Using type library API to create a connection point to the source interface representing the events. That requires a mock object, for which we need a mocking framework.
@bclothier This is what I had hoped for! Does the type library expose connection points? 😲 I had looked into it myself briefly but couldn't see where abouts connection points would be stored?
No, we need the type library API in order to obtain the definition of source interface from VBA's class that sources events. The key is that we then use the IID (the GUID of the interface) to then create a connection point. See: IConnectionPointContainer::FindConnectionPoint and also SafeEventedComWrapper as an example of how the connection points are used. The key is that you have to know the interface's IID and also have a object that implements that interface in order to handle events. The trick, however, is doing it at runtime, without knowing what the interface's shape is like until then and somehow implement it. For that reason, the C# object may need to implement the IReflect
.
Hey all,
Has rubberduck got a way of unit testing class event handler calls?
E.G.
If so, does anyone know where is this implemented in the source? 😛
If not what's the work around? Should we be creating a class and tracking this more directly?