Introduction
So, I was talking to our very own Christian Graus last night about one of the coolest, but most underused features available to us in .NET right now. We were talking about the use of the implicit
operator. Now, for those who don't know what the implicit operator is, this is what MSDN has to say about this:
"The implicit keyword is used to declare an implicit user-defined type conversion operator. Use it to enable implicit conversions between a user-defined type and another type, if the conversion is guaranteed not to cause a loss of data."
While we were talking about this, I happened to mention that I like to use implicit
in unit tests to allow me to easily build an object with mocked behavior. I then added that I like to use fluent APIs to allow me to customize tests as needed, without having to write much repetitive code. In this tip, we're going to walk though creating a simple set of tests that demonstrates the implicit operator.
The Interfaces and Implementing Them
Because I am a big fan of using interfaces and mock objects in tests, these are the interfaces we are going to create; in the grand tradition of programming manuals just about everywhere, we're going to call them IFoo
and IBar
.
public interface IFoo
{
bool ShouldAssert { get; set; }
void DoSomething();
IBar Bar { get; }
}
public interface IBar
{
}
As we can see, these are incredibly trivial interfaces. Now, we're going to implement IFoo
in a Foo
class (I love our clever naming conventions).
public class Foo : IFoo
{
private readonly IBar bar;
public Foo(IBar bar)
{
this.bar = bar;
}
public bool ShouldAssert { get; set; }
public void DoSomething()
{
if (ShouldAssert)
{
throw new InvalidOperationException();
}
}
public IBar Bar => bar;
}
Again, nothing too major here. The only thing of any real note (apart from the fact we're using C# 6 syntax for the Bar
setter), is that setting ShouldAssert
to true
will trigger an exception.
The Tests
We're going to use Visual Studio Tests with Moq here but feel free to substitute it with whichever test and mock frameworks you like. The underlying principles will be the same regardless.
Now, we're going to have three tests (not an exhaustive list of tests for Foo
I know, but handy for demonstrating the concepts). Each test will use a new Foo
instance to demonstrate the behaviors. In order to create a new instance of Foo
each time, we would do something like this:
IBar bar = new Mock<IBar>().Object;
Foo foo = new Foo(bar);
That's not too much code but imagine how much we would have to do if Foo
had a complex constructor, injecting many interfaces. Then imagine that we wanted to set the expectations of behaviors on those interfaces. Again, suppose that we need to test Foo
in multiple test classes, the code repetition is too great. Now, we could create a helper extension that is one place to build this object for us. Well, by introducing implicit
, we can do just that. Let's start off with our FooBuilder
class.
public class FooBuilder
{
private IBar bar;
private bool shouldAssert;
public FooBuilder()
{
bar = new Mock<IBar>().Object;
}
public FooBuilder WithBar(IBar bar)
{
this.bar = bar;
return this;
}
public FooBuilder WithShouldAssert(bool shouldAssert)
{
this.shouldAssert = shouldAssert;
return this;
}
public static implicit operator Foo(FooBuilder fooBuilder)
{
return new Foo(fooBuilder.bar) { ShouldAssert = fooBuilder.shouldAssert };
}
}
Let's walk through this class. We create a default boolean for shouldAssert
because we don't want to default to throwing an exception when we call DoSomething
in our Foo
class. We also provide a default mocked version of IBar
so we don't need to take care of this elsewhere if we don't need to - this would allow us to set up any expectations of behavior on IBar
that we wanted to have readily available. The real magic happens in the implicit operator - this static
method will return us a new instance of Foo
whenever we instantiate this class. Because this is implicit, there is no need to cast this back to Foo
- that's taken care for us "automagically". Now, one thing we have done here is provide the ability to override the default values if we need to. This is particularly handy when we want to do something that goes beyond the defaults. By using a Fluent API, we don't create the returning instance until we have finished the setup chain. This is very handy when we want to build arbitrarily complex behavior later on.
So, what do our tests look like?
[TestClass]
public class FooTests
{
[TestMethod]
public void TestWithDefaultBuilder()
{
Foo foo = new FooBuilder();
Assert.IsFalse(foo.ShouldAssert);
}
[TestMethod, ExpectedException(typeof(InvalidOperationException))]
public void ThrowsAssertion()
{
Foo foo = new FooBuilder().WithShouldAssert(true);
foo.DoSomething();
}
[TestMethod]
public void NewBar()
{
IBar bar = new Mock>IBar<().Object;
Foo foo = new FooBuilder().WithBar(bar);
Assert.AreEqual(bar, foo.Bar);
}
}
As we can see, we aren't casting from FooBuilder
back to Foo
- as far as the code is returned, we are returning a Foo
type here. Yes, it is possible to return different types using implicit
and following the same pattern to build them, but we don't need to do that in our tests so we aren't going to do so.
So, next time you're working on a complex system that has lots of complex interactions in your classes, have a think about whether or not this little tip could save you some heartache, grief and repetitive typing.