问题
I have constructor containing IEnumerable parameter. When I try to Inject concrete object to automocker it is not used.
When I use wrapper class containing IEnumerable property all works as expected.
How can I test TestClass1?
IEnumerable parameter
public class TestClass1
{
public TestClass1(IEnumerable<IItem> items)
{
Items = items;
}
public IEnumerable<IItem> Items { get; private set; }
}
[TestMethod]
public void TestClass1Constructor()
{
RhinoAutoMocker<TestClass1> autoMocker = new RhinoAutoMocker<TestClass1>();
IEnumerable<IItem> items = new[] { MockRepository.GenerateMock<IItem>() };
autoMocker.Inject(items);
Assert.AreEqual(1, autoMocker.ClassUnderTest.Items.Count());
}
Result of the test is:
Assert.AreEqual failed. Expected:<1>. Actual:<0>.
Wrapper class parameter
public class TestClass2
{
public TestClass2(WrapperClass numbersWrapper)
{
Items = numbersWrapper.Items;
}
public IEnumerable<IItem> Items { get; private set; }
}
[TestMethod]
public void TestClass2Constructor()
{
RhinoAutoMocker<TestClass2> autoMocker = new RhinoAutoMocker<TestClass2>();
WrapperClass numbers = new WrapperClass(new[] { MockRepository.GenerateMock<IItem>() });
autoMocker.Inject(numbers);
Assert.AreEqual(1, autoMocker.ClassUnderTest.Items.Count());
}
Result of the test is:
Success.
回答1:
After taking a look at the source code for the AutoMocker<TTargetClass> class, I noticed the following:
- AutoMocker uses a StructureMap container under the covers
- usually all dependencies are directly resolved using the container
- dependencies that are of type
IEnumerable<T>
are treated differently (see below)
Here's a piece of code from the AutoMocker<TTargetClass>
class that shows how constructor dependencies are resolved (I removed some lines for brevity):
private object[] getConstructorArgs()
{
ConstructorInfo ctor = Constructor.GetGreediestConstructor(typeof (TTargetClass));
var list = new List<object>();
foreach (ParameterInfo parameterInfo in ctor.GetParameters())
{
Type dependencyType = parameterInfo.ParameterType;
if (dependencyType.IsArray)
{
[...]
}
else if (dependencyType.Closes(typeof (IEnumerable<>)))
{
Type @interface = dependencyType.FindFirstInterfaceThatCloses(typeof (IEnumerable<>));
Type elementType = @interface.GetGenericArguments().First();
// Here's the interesting code:
var builder = typeof (EnumerableBuilder<>).CloseAndBuildAs<IEnumerableBuilder>(_container,
elementType);
list.Add(builder.ToEnumerable());
}
else
{
object dependency = _container.GetInstance(dependencyType);
list.Add(dependency);
}
}
return list.ToArray();
}
The code shows that dependencies are usually resolved using _container.GetInstance
, but there are two exceptions: ararys and IEnumerable<>
s
For IEnumerable<T>
, it turns out that _container.GetAllInstances(typeof(T))
is used. This meas that in your case you should inject several IItem
instances, not an IEnumerable<IItem>
. The code responsible for this is the EnumerableBuilder<T>
class, whih can be found in the same file (at the end).
OK, enough talk. I'm not sure if my explanation is clear enough, so below is code for two tests that pass. Hopefully that will clarify everything:
[Test]
public void Test_OneItem()
{
var autoMocker = new RhinoAutoMocker<TestClass1>();
autoMocker.Inject(MockRepository.GenerateMock<IItem>());
Assert.AreEqual(1, autoMocker.ClassUnderTest.Items.Count());
}
[Test]
public void Test_TwoItems()
{
var autoMocker = new RhinoAutoMocker<TestClass1>();
autoMocker.Inject(MockRepository.GenerateMock<IItem>());
autoMocker.Inject(MockRepository.GenerateMock<IItem>());
Assert.AreEqual(2, autoMocker.ClassUnderTest.Items.Count());
}
来源:https://stackoverflow.com/questions/15761090/how-structure-map-automocker-inject-works