The problem I am facing currently is that I and my team are working simultaneously on interdependent pieces of the system under development. My piece was depending on a piece of my colleague's and one of his methods was throwing a "NotImplementedException".
Wanted to mock just that method so I could proceed with testing my work.
Here's how the code looks in Rhino:
1 using System;
2 using Microsoft.VisualStudio.TestTools.UnitTesting;
3 using Rhino.Mocks;
4
5 namespace ThinkFarAhead.LearningMocking
6 {
7 [TestClass]
8 public class TestRhinoAndMoq
9 {
10 [TestMethod]
11 public void TestPartialMock()
12 {
13 Demo partialMock = new MockRepository().PartialMock<Demo>();
14 partialMock
15 .Stub<Demo, string>(x => x.ReturnStringNoArgs())
16 .Do((Func<string>)delegate { return "somevalidstring"; });
17
18 partialMock.Replay();
19 Assert.AreEqual(partialMock.ReturnStringNoArgs(), "somevalidstring");
20 }
21
22 [TestMethod]
23 public void TestPartialStub()
24 {
25 IDemo partialStub = MockRepository.GenerateStub<IDemo>();
26 partialStub
27 .Stub<IDemo, string>(x => x.ReturnStringNoArgs())
28 .Do((Func<string>)delegate { return "somevalidstring"; });
29
30 partialStub.Replay();
31 Assert.AreEqual(partialStub.ReturnStringNoArgs(), "somevalidstring");
32 }
33 }
34
35 public interface IDemo
36 {
37 string ReturnStringNoArgs();
38 string ReturnStringWithInt32Arg(int arg);
39 }
40
41 public class Demo : IDemo
42 {
43 public virtual string ReturnStringNoArgs()
44 {
45 throw new NotImplementedException();
46 }
47
48 public string ReturnStringWithInt32Arg(int arg)
49 {
50 return string.Format("{0}{1}", new string('0', 6 - arg.ToString().Length), arg);
51 }
52 }
53 }
As you can see both the tests pass. Let's remove 'virtual' from:
45 public string ReturnStringNoArgs()
Now, as expected, TestPartialMock fails.
Test method ThinkFarAhead.LearningMocking.TestRhinoAndMoq.TestPartialMock threw exception: System.NotImplementedException: The method or operation is not implemented..
Oh yeah, we've been told that it's a constraint imposed by Castle Dynamic Proxy [actually CLR]. Hey, I don't want to fight the system, all I want is to wring the functionality I need from the framework.
Also, consider the below behaviour:
System.Diagnostics.Debug.WriteLine(partialMock.ReturnStringWithInt32Arg(20)); // will print "000020"
System.Diagnostics.Debug.WriteLine(partialMock.ReturnStringWithInt32Arg(30)); // will print "000030"
With stub, you will have to setup two different expectations: [I'm not sure I need to! I've posted this question on the forum. Will provide the link later]
partialStub
.Stub<IDemo, string>((Func<IDemo, string>)delegate(IDemo x) { return x.ReturnStringWithInt32Arg(20); })
.IgnoreArguments()
.Do((Func<int, string>)
delegate(int arg)
{
return string.Format(
"{0}{1}",
new string('0', 6 - arg.ToString().Length), arg);
}
);
partialStub
.Stub<IDemo, string>((Func<IDemo, string>)delegate(IDemo x) { return x.ReturnStringWithInt32Arg(30); })
.IgnoreArguments()
.Do((Func<int, string>)
delegate(int arg)
{
return string.Format(
"{0}{1}",
new string('0', 6 - arg.ToString().Length), arg);
}
);
partialStub.Replay();
System.Diagnostics.Debug.WriteLine(partialStub.ReturnStringWithInt32Arg(20)); // will return "000020"
System.Diagnostics.Debug.WriteLine(partialStub.ReturnStringWithInt32Arg(30)); // will return "000030"
Even if I do not have to setup multiple times, once for each argument, if the logic is non-trivial, replicating the same logic on the Stub method would be VERY inconvenient. Not to mention dependencies if needed. Stub per Fowler's definition is supposed to just return "canned" responses right? [Aside: This is a classic example of theory superceded by common sense. If you're supposed to return "canned" responses, you better be prepared to anticipate each argument. Guess that's what Fakes do. We're trying to implement a 'Fake' by using Rhino's Stub. Oh, well, we're getting derailed by jargon now]
OK, where does it all lead to? Well, It seems we need a PartialStub [Jargon again? :)]. Hm...or is there a better way?
How about this: One that will delegate [not the C# one, the one that managers practice ;)] calls to the original object when no expectations are set. And override them when specified.
Let's define our "wanna be" syntax, shall we?
With Rhino:
[TestMethod]
public void TestPartialStub()
{
Demo demo = new Demo();
IDemo partialStub = MockRepository.GenerateStub<IDemo>(demo);
partialStub
.Stub<IDemo, string>(x => x.ReturnStringNoArgs())
.Do((Func<string>)delegate { return "somevalidstring"; });
partialStub.Replay();
//Route calls to the actual object
Assert.AreEqual(partialStub.ReturnStringWithInt32Arg(20), "000020");
Assert.AreEqual(partialStub.ReturnStringWithInt32Arg(30), "000030");
//Call the Stub override
Assert.AreEqual(partialStub.ReturnStringNoArgs(), "somevalidstring");
}
[TestMethod]
public void TestPartialStubMoq()
{
Demo demo = new Demo();
Mock<IDemo> repo = new Mock<IDemo>(demo);
IDemo partialStub = repo.Object;
repo.Expect<string>(x => x.ReturnStringNoArgs()).Returns("somestringbydefault");
Assert.IsTrue(partialStub.ReturnStringWithInt32Arg(10), "000010");
Assert.IsTrue(partialStub.ReturnStringNoArgs(), "somestringbydefault");
}
I would like to add this functionality to both Rhino & Moq with both Ayende and Daniell asking me to contribute. Hope I'll get something done over the weekend. Let me know what you think.
2 comments:
Good luck with the patch.
You could use Typemock (with whom I'm associated with) and
1. Just stub/mock the non virtual methods.
2. Use "live" objects (your proposed syntax):
{
Demo demo = new Demo();
// Start Mocking
using (RecordExpectations recorder = new RecordExpectations())
{
recorder.ExpectAndReturn(demo.ReturnStringNoArgs(), "somestringbydefault").RepeatAlways();
}
}
3. Use the new AAA Syntax
Demo demo = Isolate.Fake.Instance<Demo>( Members.CallOriginal);
Isolate.WhenCalled(() => demo.ReturnStringNoArgs()).WillReturn("somestringbydefault");
Eli, That's cool! I did hear TypeMock's very powerful. It's actually one of the [leading] contenders for our project [which I haven't gotten around to evaluate].
#2
--
So, I am not constrained by having to declare the method 'virtual'! Does this mean I can mock 'internal' / 'private' too?
#3
--
Fakes, uh? I didn't know what I was trying to do was a fake! Guess it's "working implementation & not production strength" and hence a fake :)
Post a Comment