A couple of weeks ago, I blogged about a problem I was having that took more time to solve then expected; it was a task that I thought would be fast but turned out not to be. I jokingly described it as the sort that costs you time you would have otherwise spent on a coffee break. Well, unfortunately, I ran into another such dilemma this week, so I thought I would collect the descriptions of these time sinks into an ongoing series which I'll call Coffee Breaks. In it, I'll present the time consuming problems that I bump into, and how I solved them. To kick it off, I give you the second in this new series:
This week, I needed to write a unit test that would isolate a LINQ to SQL DataContext, allowing me to call the methods that mapped my stored procedures and return known data instead of information in a database. This would allow me to test my data access logic without having to establish a connection to a back-end server. To separate my DataContext, I wanted to use TypeMock Isolator 5.1. This seemly simple problem ended up taking a while to solve because A) I'm new to TypeMock Isolator and wasn't familiar with its API, and B) because the SPROC wrappers returned an ISingleResult<T> which I couldn't simply replace with an array or something.
Learning a new library just takes time. While learning Typemock's and applying it to this problem, however, I ran into a couple of obstacles that protracted the effort and eventually cost me my coffee break (so to speak). The issues where these:
- In order to substitute my known list of results for those returned by the SPROC wrapper method, I had to create a dynamic mock and figure out how to inject my data into it.
- If I tried to swap in an array when GetEnumerable was called on the dynamic mock, I kept getting a StackOverflowException when executed inside Resharper's test runner.
To elaborate a bit, let me begin by showing the DataContext definition that was generated when I drug the stored procedure from the Server Explorer to the ORM designer:
1 public partial class MyGoodDataContext : DataContext
2 {
3 [Function(Name = "dbo.MyGoodStoredProcedure")]
4 public ISingleResult<MyGoodStoredProcedureResult> MyGoodStoredProcedure()
5 {
6 IExecuteResult result = this.ExecuteMethodCall(this,
7 ((MethodInfo)(MethodInfo.GetCurrentMethod())));
8 return ((ISingleResult<MyGoodStoredProcedureResult>)(result.ReturnValue));
9 }
10 }
Given this, I had to figure out a way to replace ISingleResult<T> with some object that contained my test data. The only concrete implementation of ISingleResult<T> in the .NET framework is internal, so I tried to create a fake one and replace the original one with my test double using TypeMock Isolator's SwapNextInstance method like this:
var fake = Isolate.Fake.Instance<ISingleResult<MyGoodStoredProcedureResult>>();
Isolate.SwapNextInstance<ISingleResult<MyGoodStoredProcedureResult>>().With(fake);
This produced a run-time error telling me to use the MockObject method on the MockManager class to create a dynamic mock instead (perfect error message BTW!).
After some fiddling, I figured out this class's API, and the necessity to initialize it via attributes or by an explicate call to its Init method. Afterward, I was able to create a dynamic mock object of type IResultSet<T>. Now I had to somehow cause my canned info to be returned when this object was enumerated. To this end, I replaced the return value of the mock object's GetEnumerable method with my data like this:
results.ExpectAndReturn("GetEnumerator", expected.GetEnumerator(), 1);
At first, the variable "expected" was an anonymous array of MyGoodStoredProcedureResult objects. However, every time that I enumerated this collection in the code under test, a StackOverflowException was through and my test runner would crash. I replaced it with a object of type List
With those issues resolved, I was able to fake my DataContext and isolate it from the database using this unit test:
1 [Test, VerifyMocks, Isolated]
2 public void Test()
3 {
4 // Arrange
5 var dataContext = Isolate.Fake.Instance<MyGoodDataContext>(Members.ReturnRecursiveFakes);
6 var results = MockManager.MockObject<ISingleResult<MyGoodStoredProcedureResult>>();
7 var expected = GetExpectedResults();
8
9 results.ExpectAndReturn("GetEnumerator", expected.GetEnumerator(), 1);
10 Isolate.SwapNextInstance<MyGoodDataContext>().With(dataContext);
11 Isolate.WhenCalled(() => dataContext.MyGoodStoredProcedure()).WillReturn(results.Object);
12
13 // Act
14 var actual = MyGoodRepository.MethodThatInTurnCallsMyGoodStoredProcedure();
15
16 // Assert
17 for (var i = 0; i < actual.Count; i++)
18 Assert.That(actual[i], Is.EqualTo(expected[i]));
19 }
Problem solved, but not before coffee time was over :-(