Reputation: 15282
[InheritedExport(typeof(MyAbstractClass))
public abstract class MyAbstractClass
{
[Import] protected IFoo Foo { get; private set; }
}
public sealed class MyClass : MyAbstractClass
{
public void DoSomething()
{
if (Foo == null) throw new Exception();
var = Foo.GetBar();
//etc.
}
}
Basically, I use MEF to export classes out and get "common" imports. When I want to test these classes I can create mock interfaces of IFoo, but how do I actually get it in there with the private setter? MEF somehow is able to handle it, and I'm not sure how else I can test my DoSomething method.
Upvotes: 1
Views: 1383
Reputation: 1759
reflection is the best way to do it. i like to create an extension method in a base test assembly with useful functions like accessing/setting private members and such.
another option (if it is acceptable to make the setter protected instead of private - which may or may not be the case here, but if you do have a protected member with a similar desire) would be have your test subclass the class under test. it feels dirty and doesn't seem like a good idea, but i can't think of a practical reason why it's bad and would accomplish the objective here.
public static class ReflectionExtensions
{
public static T GetPrivateFieldValue<T>(this object instance, string fieldName)
{
var field = instance.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
if (field != null)
{
return (T) field.GetValue(instance);
}
throw new ArgumentException("The field specified could not be located.", "fieldName");
}
public static void SetReadonlyProperty(this object instance, string propertyName, object value)
{
instance.GetType().GetProperty(propertyName).SetValue(instance, value, null);
}
public static void SetStaticReadonlyProperty(this Type type, string propertyName, object value)
{
type.GetProperty(propertyName).GetSetMethod(true).Invoke(null, new[] { value });
}
}
Upvotes: 1
Reputation: 241641
MyAbstractClass
has a dependency on IFoo
but you're not making it explicit. You should add a constructor to make the dependency explicit:
public MyAbstractClass(IFoo foo) { this.Foo = foo; }
Now you can easily test it using your mock.
So, I'd rewrite your class like this:
[InheritedExport(typeof(MyAbstractClass))
public abstract class MyAbstractClass {
private readonly IFoo foo;
public IFoo Foo {
get {
Contract.Ensures(Contract.Result<IFoo>() != null);
return this.foo;
}
}
protected MyAbstractClass(IFoo foo) {
Contract.Requires(foo != null);
this.foo = foo;
}
}
public class MyClass : MyAbstractClass
{
[ImportingConstructor]
public MyClass(IFoo foo) : base(foo) { }
}
Otherwise, you have to use reflection to get at the private setter. That's disgusting.
Upvotes: 1
Reputation: 32515
If you want to preserve your MEF Imports, the easiest way to do so is to use the ImportingConstructorAttribute
instead of ImportAttribute
s on each property.
[InheritedExport(typeof(MyAbstractClass))
public abstract class MyAbstractClass
{
[ImportingConstructor]
protected MyAbstractClass(IFoo foo)
{
//BONUS! Null check here instead...
if (foo == null) throw new NullArgumentException("foo");
Foo = foo;
}
protected IFoo Foo { get; private set; }
}
public sealed MyClass : MyAbstractClass
{
[ImportingConstructor]
public MyClass(IFoo foo) : base(foo) { }
public void DoSomething()
{
var = Foo.GetBar();
//etc.
}
}
The solution kind of stinks because now you have to have all classes that extend from MyAbstractClass each use an ImportingConstructorAttribute
and call base()
. This can get quite ugly if your abstract class is used all over and especially if it decides to add another imported property... now you have to change the constructor signature.
I'd stick with ugly reflection... better ugly unit tests than ugly code.
Upvotes: 3
Reputation: 2975
I believe you can accomplish this with the MS Moles framework:
http://research.microsoft.com/en-us/projects/moles/
Upvotes: 1
Reputation: 112342
Try to pass it in the constructor:
class MyClass : MyAbstractClass
{
public MyClass (IFoo foo)
{
Foo = foo;
}
}
and change the "private set" in your abstract class to "protected set".
Upvotes: -1