How to replace an exported part / object in a MEF container?
I have a WPF app running, which needs all operations that affect the UI to be on the UI Thread. WPF also provides a Dispatcher class that handles this - so I extracted that into a dependency.
public interface UIActionExecutor
{
void Do(Action action);
}
So in my prod开发者_JAVA百科uction code, I use an exported implementation which delegates to the WPF Dispatcher. I'm using MEF for DI.
Now the problem, in my acceptance tests, I need to replace the part / object in the container that responds to UIActionExecutor
by a Mock. So I need to remove ExecutorUsingWpfDispatcher
from my container and add MockUIActionExecutor
in its place. This sounds pretty simple (if I was not using MEF)... but my searching skills haven't helped me find an answer as to how to do this with the MEF container ?
Update: If anyone wants to know why/how the solution works - read Glenn Block's blog post#2. This is what I ended up using
var defaultExportProvider = new CatalogExportProvider(__defaultCatalog);
var catalogOfMocks = new AssemblyCatalog(assemblyExportingMocks);
// order of params important (precedence left to right)
__container = new CompositionContainer(catalogOfMocks, defaultExportProvider);
defaultExportProvider.SourceProvider = __container
A DI container is responsible for wiring everything together.
A unit test is responsible for testing a single unit of code in isolation. Mocks are used to replace dependencies. So in principle a DI container should not be used in a unit test. It contradicts the definition of "unit test".(¹)
However, I can certainly understand that you might want to do automated integration tests in addition to unit tests, and you might want to use MEF yet replace certain MEF parts in such a test. You can do that like this:
// first set up the main export provider
var mainCatalog = new AggregateCatalog(...);
var mainExportProvider = new CatalogExportProvider(mainCatalog);
// now set up a container with mocks
var mockContainer = new CompositionContainer();
mockContainer.ComposeExportedValue<IFoo>(fooMock);
mockContainer.ComposeExportedValue<IBar>(barMock);
// use this testContainer, it will give precedence to mocks when available
var testContainer = new CompositionContainer(mockContainer, mainExportProvider);
// link back to the testContainer so mainExportProvider picks up the mocks
mainExportProvider.SourceProvider = testContainer;
(¹)Judging from your blog, I'm sure you already know this. But others will also read this answer, so I wanted to be clear about the term "unit test".
Could not get the accepted solution to work. Below code should work and precedence is described in the documentation for AggregateExportProvider
.
var mainContainer = new CompostionContainer();
mainContainer.ComposeExportedValue<IFoo>(new Foo() {Test = 1});
var mockContainer = new CompositionContainer();
mockContainer.ComposeExportedValue<IFoo>(new Foo() {Test = 2});
var aggregateExportProvider = new AggregateExportProvider(
mockContainer, // IFoo in this container takes precedence
mainContainer);
IFoo foo = aggregateExportProvider.GetExportedValue<IFoo>();
Console.WriteLine(foo.Test); // Outputs: 2
精彩评论