Particular / NServiceBus

Build, version, and monitor better microservices with the most powerful service platform for .NET
https://particular.net/nservicebus/
Other
2.08k stars 648 forks source link

Setting Timeout on mocked IBus causes "bus does not implement IManageMessageHeaders" #2614

Closed ianbattersby closed 9 years ago

ianbattersby commented 9 years ago

NB: Working against 'develop' branch

We are currently upgrading from v4 to v5 and have some unit tests that use Moq to pass in a Mock<IBus>. All tests pass apart from those setting timeouts where the exception "bus does not implement IManageMessageHeaders" is thrown:

nservicebusmanagemessaheheaderserroronmock

Looking at the source I can see IManageMessageHeaders is only implemented against UnicastBus (not IBus) , so suspect I'm missing something, or should be perhaps using a testing support library for this mock? Guidance much appreciated.

ramonsmits commented 9 years ago

@ianbattersby What probably fixes this is to Moq multiple interfaces with .As

We also have a testing framework to test handlers and sagas. http://docs.particular.net/nservicebus/unit-testing

The second option requires all your current tests to be converted. It is probably best to test your handlers and sagas this way as it forces you to test in small units of execution so you can really do isolated behavioral testing.

Let me know if this resolves your problem.

ianbattersby commented 9 years ago

@ramonsmits Erm, thanks for the suggestion but I don't think As<>() is going to help in this scenario, you can only add interfaces with As<>() and not concrete types such as UnicastBus (which seems to have no unifying interface).

Testing the handler using this vanilla-style is the ultimate isolation IMHO - especially breaking down tests to cover deeper execution (e.g. verifying a factory method has been called with expected arguments). I appreciate this may be too deep for some but we occasionally do this. The testing library looks good at testing a scenario of a saga (something we normally cover in Acceptance Tests) but adds an additional layer of abstraction that if we don't need I'd rather not use. Also, conversion to the testing library would be huge in our 7+ repositories.

I'm not sure of a way round this without implementing IUnicastBus interface unifying IStartableBus, IInMemoryOperations, and IManageMessageHeaders? Even if the testing library would be your preferred usage I would see the inability to unit-test sagas/handlers in a vanilla form a limitation.

ramonsmits commented 9 years ago

@ianbattersby Did you try the .As<IManageMessageHeaders> because the screenshot that you added clearly shows that an exception is raised because the given bus does not implement IManageMessageHeaders. Adding this interface should result in a fake IManageMessageHeaders and not throw the InvalidOperationException.

As you are unittesting you probably only want to use fakes on the your current unittests.

Also, from my personal experience in unittesting handlers is that you can easily use Moq for testing behavior based on a incoming message to see how it interacts with its dependencies like repositories, specifications, calculations, etc. Just use a fake IBus implementation.

You want to use the NServiceBus.Testing framework when you want to validate a handlers behavior in regards to its injected IBus. The reason for that is that there are multiple methods to send or publish a message. The testing framework makes it more easy to just test if a message is published or send without knowing which method is called. This makes these test less fragile when you refactor a handler where its behavior stays the same but using a different implementation.

ianbattersby commented 9 years ago

@ramonsmits After some experimenting seems my confusion stems from var myMock = new Mock<IBus>().As<IManageMessageHeaders>(); resulting in a Type of Mock<IManageMessageHeaders> whereas doing var myMock = new Mock<IBus>(); myMock.As<IManageMessageHeaders>() keeps the Type intact as IBus. Huzzah!

So I now have this working using;

this.bus = new Mock<IBus>();
this.bus.As<IManageMessageHeaders>().Setup(m => m.SetHeaderAction).Returns((o, s, v) => { });

Noted your comment about faking out IBus opposed to mocking, we do have a fake IBus originating from @danielmarbach but many codebases still use Moq. Also appreciate benefits and lack of brittleness of using NServiceBus.Testing, I suspect if we didn't have full acceptance tests we'd undoubtedly have followed this route.

Thanks for the help!