RETURN OF THE IMOCK
I did a twitter where I mentioned I'd updated my IMock packages. These are for Nuget and ReSharper.
What the fuck is IMock?
First - Rude.
I wrote about it a couple years ago when I did an IMock Announcement. Which... on review... I dropped the fucking ball. I didn't get ANYTHING up about it.
WELL... TODAY I shall remedy that!
History
Around Mid-2017 I was tasked to lead development of a mobile app for the Windows Store using UWP. Some highlights of that can be seen in my Experience Report about howe we achieved feature parity in 25% of the developer hours.
IMock isn't touched on there, but it's the core of what I do now as MicroObjects, so I like to share it. :)
Writing Tests
During development, we used XP practices, Mobbing, TDD, and other well known good practices. We used them strictly. Again, it's what got me to the style of working and coding that I do now.
What I found during the testing is that mocking frameworks, like Moq, couldn't be used on a UWP codebase. The Reflection.Emit
that they use isn't allowed. Checking on StackOverflow there appears to be a few other options... none I liked (or found?) at the time.
I went with another well known practice of creating fakes. Instead of Mocking shit, I used fakes. I'd create an implementation of the interface that did exactly what's required for the test.
interface IExample{
bool ShouldThisHappen();
}
If the test needed it to return false
class TheTestClass{
...
private class FakeExample : IExample{
bool ShouldThisHappen() => false;
}
}
This works great. It's very clear what's happening. There's no logic, does what the test needs.
What if the testing also needs a true response?
class TheTestClass{
...
private class FakeExample : IExample{
FakeExample(bool shouldThisHappenResult) => _shouldThisHappenResult = shouldThisHappenResult
bool ShouldThisHappen() => _shouldThisHappenResult;
}
}
OK... pretty simple.
What if it needs to throw an exception?
... Well...
class TheTestClass{
...
private class FakeExample : IExample{
FakeExample(bool shouldThisHappenResult) => _shouldThisHappenResult = shouldThisHappenResult
bool ShouldThisHappen() => _shouldThisHappenResult;
}
private class FakeExampleThrowsException : IExample{
bool ShouldThisHappen() => throw new Exception();
}
}
COPY PASTE GUT
I did a lot of copy/paste/gut of Mock classes. Anytime you copy/paste... maybe there's an abstraction hiding. I started to look and I did see one. Quite a few itterations got me through the following thinking...
What if I need it to return true
and then false
... like if it's a HasMoreResults
method...
... Fuck.
This had to be solved because there were situations like this in the code.
We got a pretty good solution to it.. if I may say so myself. (Note: Some details fudged for clarity)
class TheTestClass{
...
private class FakeExample : IExample{
int _next = 0;
bool SetShouldThisHappen(params bool[] valuesToReturn) _valuesToReturn = valuesToReturn;
bool ShouldThisHappen() => _valuesToReturn[next];
}
}
This allows us to define the values we want to have in the test. It didn't do the exception though...
class TheTestClass{
...
private class FakeExample : IExample{
int _next = 0;
bool SetShouldThisHappen(params Func<bool>[] funcs) _valuesToReturn = valuesToReturn;
bool ShouldThisHappen() => _valuesToReturn[next]();
}
}
So we made them Func
s. Now we could do... whatever. It gave us A LOT of freedom in how we did our tests. I had an easier time using the final form than I have had with ANY other moq/test framework.
What this actually looks like now, with all of the wonderful complexity abstracted away by the framework
IExample mockExample = new MockExample.Builder().ShouldThisHappen(true, true, true, false).Build)();
If you want to do something that isn't the return type
IExample mockExample = new MockExample.Builder().ShouldThisHappen(new Func<bool>(()=>true)), new Func<bool>(()=>throw new Exception()))).Build)();
If there are more methods, it's a fluent interface, so keep going.
IExample mockExample = new MockExample.Builder()
.ShouldThisHappen(new Func<bool>(()=>true)), new Func<bool>(()=>throw new Exception())))
.OtherMethod(...)
.DoStuff(...)
.Build)();
Simple and clear. It also gets rid of the fucking noise that other frameworks have mock.Setup(x => x.TheFunction(args)).ThenReturn(blah)
. It's so much clutter.
Yes, I have a bias for IMock, but part of the API design was driven by the elements I didn't like, and didn't need, from other frameworks I've used heavily in the past.
Creating the IMock Implementation
All of that is available from the Nuget Package.
The bits just need to be put together for each method. For the IExample
interface with a single method, this is what gets generated
public sealed partial class MockExample : IExample
{
private MockMethodWithResponse<bool> _shouldThisHappen;
private MockExample()
{
}
public bool ShouldThisHappen() => _shouldThisHappen.Invoke();
public void AssertShouldThisHappenInvoked() => _shouldThisHappen.AssertInvoked();
public sealed class Builder
{
private readonly MockMethodWithResponse<bool> _shouldThisHappen = new MockMethodWithResponse<bool>("MockExample#ShouldThisHappen");
public MockExample Build()
{
return new MockExample { _shouldThisHappen = _shouldThisHappen, };
}
public Builder ShouldThisHappen(params bool[] responseValues)
{
_shouldThisHappen.UpdateInvocation(responseValues);
return this;
}
public Builder ShouldThisHappen(params Func<bool>[] responseValues)
{
_shouldThisHappen.UpdateInvocation(responseValues);
return this;
}
}
}
It's a partial class as I've ocassionally needed to add customization to a particular mock, and didn't want worry about it getting nuked while using the ReSharper plugin (it has an option to update the mock impl).
This is a nice simple class. And no, it doesn't really get any "better" when you have lots of methods
I have a Test Interface Implementation that shows everything I expect to get generated does.
You might imagine that creating one of these Mocks takes a while. And yes... it does. When we started, it took about 30 minutes to build one up.
That might seem like a lot of time to create a test class; but it saved us SO MUCH time in the end. And gave us absolute confidence in the behavior of our code. Our Mocks have ZERO logic in them. No switches or arguments, or multiple of them... IMock gives us clean and clear classes we can use to mimic any and all behavior our system could do.
Now, 30 minutes is a while. We didn't like the mind-numbing waste of time. There's a lot of patterns in the generated mock and we were able to use templates in ReSharper to do most of the work for us. Got it down to about 5 minutes per class. HUGE time savings.
This project had almost 1500 classes at finish. We refactored ruthlessly. We didn't use any DI Frameworks, we did Manual Dependency Injection with constructor chaining. You can read about that in some of my blog posts... somewhere. As we developed each feature, we had to create a lot of classes and a lot of Mocks.
Once we had them, development was a breeze. The time spent, absolutely worth it.
Still a bit of a pain to do the repetative thing with the templates, over and over...
Personal Interest
I wanted to write a plugin for ReSharper for a while... this seemed like a good opportunity to explore that. I was only active on the code AT MOST 10% of the time (proving anyone could follow the practices and kick ass). I did most of the development at home since it was a personal interest.
Once it got working, it took the time down to about zero. It takes longer to pop up the ReSharper suggestion and select it than to generate the Mock code, so... inconsequential amount of time.
Once we started using it, creating the Mocks was a non-issue. Fastest most valuable tooling (aside from ReSharper itself) on the project.
That's the source of the IMock ReSharper Plugin. It auto creates the Mock code for you.
Limitations
It only works on interfaces. The way I write code, this isn't a limitation; but it is of the tool itself. If you don't use interfaces, you get nothing! If you think interfacing everything is HORRIBLE AND WRONG - Neato, You get nothing!
This was developed to support the way we work and the code we write. And it makes it so clean...
It doesn't do eventing. I don't do a lot of events in my code, so this doesn't either. I made an effort to get eventing in when I was fiddling with some of the UI stuff... it just never did.
OH - Events are why I had to make it a partial class. The tool doesn't generate them, so you need to handle it in some additional code. If I have to use them at somepoint, maybe I'll see the abstraction required... but since I don't... You get nothing!
I'm not trying to add everything to this tool. It does what I need it to, and does it really damn well. More than that... you get nothing!
Enough?
I hope that's a bit on the tool. I know I should have a more better proper write up... but I'm so lazy...
Anyway... this is more than I've written before, I think... so... ENJOY!