Caliburn-Micro / Caliburn.Micro

A small, yet powerful framework, designed for building applications across all XAML platforms. Its strong support for MV* patterns will enable you to build your solution quickly, without the need to sacrifice code quality or testability.
http://caliburnmicro.com/
MIT License
2.79k stars 776 forks source link

Support for auto-mocking of interfaces done automatically by container #797

Open tapika opened 2 years ago

tapika commented 2 years ago

Normally when you want to perform unit testing - instead of using normal implementation - you use new Mock<interface> object instead.

SimpleContainer operates on real implementations obtained from real assembies, real interfaces.

Would it be possible to create such auto-mocking container so container itself would instantiate mocks as implementation requires ?

In theory if we have:

class Class1
{
     public Class1( Interface1 i1, Interface i2, Interface i3 );
}

Normally when doing normal implementation, you could write something like this:

var class1 = new Class1( IoC.Get<Interface1>(), IoC.Get<Interface2>(), IoC.Get<Interface3>() );
(*1)

(Or similar SimpleContainer calls)

When testing - you can either instantiate new Mock's for each interface and register to container (manual work), or container itself could automatically create new Mocks for each interface.

// If type not registered - automock it
MockingSimpleContainer cont ( (type) => { return new Mock<type>() };

var class1 = cont.New<Class1>();
This would have similar effect as in (*1) - only in unit test application, so everything would be auto-mocked.

This is of course pseudo code, in reality new Mock would require figuring out Mock constructor method and call it (see https://docs.microsoft.com/en-us/dotnet/framework/reflection-and-codedom/how-to-examine-and-instantiate-generic-types-with-reflection)

Using this kind of approach could simplify test writing.

KasperSK commented 2 years ago

What is to hinder that you register the Mock implementation in the container? It would make sense to have a composition root for testing. Also the SimpleContainer is just as it implies supposed to be simple that is why Caliburn.Micro can be extended to use other IoC containers. I am not sure it would be valuable to build in support for automatic Mocks.

tapika commented 2 years ago

What is to hinder that you register the Mock implementation in the container?

That can be done as well. But why container cannot do this automagically ? I guess container has knowledge on interfaces which it supports, as well it can get also information on class which it needs to instantiate. The rest is more or less computable. Only thing which is missing is maybe interfaces caliburn does not have - then it could query via separate callback function what to do about them.

tapika commented 2 years ago

https://github.com/tapika/swupd/blob/master/src/chocolatey.tests2/infrastructure.app/configuration/CommandContext.cs#L202

=> Instantiate mock container dynamically, mock type is determined at run-time.

https://github.com/tapika/swupd/blob/master/src/chocolatey.tests2/infrastructure.app/configuration/CommandContext.cs#L213

=> Potentially plug it under logger context.

CommandContext is such example of such dynamic mocking container, which I would like to see in context of IoC container as well.

Dynamically created mock can be used then later on by the test itself.

"container" side:

https://github.com/tapika/swupd/blob/master/src/chocolatey.tests2/infrastructure.app/configuration/CommandContext.cs#L60

client side:

https://github.com/tapika/swupd/blob/master/src/chocolatey.tests2/infrastructure.app/configuration/TestChocolateyOptionSet.cs#L134

Documentation on everything if someone wonders how everything ties together:

https://github.com/tapika/swupd/blob/master/docs/testing.md

(Irrelevant from this ticket perspective)