One thing I notice is that all of my tests that don't use Rhino mocks
have this structure:
[Test] public void SomethingShouldDoSomething()
{
//build
//operate
//check state
}
and my tests that do use Rhino have this structure:
[Test]public void SomethingShouldDoSomething()
{
//build
//replay
//operate
//verify
}
Having these four distinct parts of each test is a little bit
confusing unless you comment them as such (and I believe in "comments
lie, so don't use them"). I'm experimenting w/ something that ends up
looking like this:
[Test]public void SomethingShouldDoSomething()
{
using (SetExpectations.Do(mocks))
{
//build
}
using (Execute.Do(mocks))
{
//operate
}
}
The two using blocks result in a call to mocks.ReplayAll() and
mocks.VerifyAll(). I realize that you just took pains to remove these,
but I wonder if anyone else sees this as useful, has any naming
recommendations, etc.
Someday, C# will support blocks and we'll be able to do this:
[Test]public void SomethingShouldDoSomething()
{
SetExpectations
{
//build
}
Execute
{
//operate
}
}
That seems more clear. Someday. But in the mean time....
I recommend that you would do something like:
[Test]public void SomethingShouldDoSomething() {
//build
//replay
//operate
}
[TearDown] public void TearDown() {
mocks.VerifyAll();
Anonymous delegates wouldn't have the same problem as "using" but then
it would be even more verbose. It's almost too bad that running code
can't ask whether an exception is currently being propagated. That
would solve this problem neatly (but perhaps introduce others...).
Of course, with the new MockRepository and VerifyAll in the base-class,
all you're really getting out of this is a ReplayAll and some visual
isolation, which isn't always what you want anyways. Doesn't seem to
buy much more than comments do.
Jeff.
public class SetExpectations : IDisposable
{
private MockRepository mocks;
public SetExpectations(MockRepository mocks)
{ this.mocks = mocks; }
public void Dispose()
{ mocks.ReplayAll(); }
}
public class RunTest : IDisposable
{
private MockRepository mocks;
public RunTest(MockRepository mocks)
{ this.mocks = mocks; }
public void Dispose()
{ mocks.VerifyAll(); }
}
[Test] public void TestSomething()
{
using (new SetExpectations(mocks))
{ //build }
using (new RunTest(mocks))
{ //operate }
}
So I think it may depend on where/how you implement things. If you
were to use the above classes (with better names) and implement
methods in MockRepository that returned them, you could get this:
[Test] public void TestSomething()
{
using (mocks.SetExpectations())
{ //build }
using (mocks.RunTest())
{ //operate }
}
Now I'm not saying that this is ideal - but I think it's doable. The
question is not IF we can do it - the question I have is "does anyone
like the idea?"
[Test] public void TestSomething()
{
Expect.Call(mockA.doSomething(x,y)).Return(z);
mocks.ReplayAll();
realB.doSomething(e,f);
mocks.VerifyAll();
}
Here's my experience (w/ the exact code copied from VS):
=============================
using System;
using NUnit.Framework;
using Rhino.Mocks;
namespace Something
{
[TestFixture]
public class SomethingTest
{
MockRepository mocks;
[SetUp]
public void SetUp()
{
mocks = new MockRepository();
}
[Test]
public void ThrowExceptionInBlock()
{
using (new SetExpectations(mocks))
{
throw new ApplicationException("exception thrown in test method");
}
}
[Test]
public void ThrowExceptionFromMethodCall()
{
using (new SetExpectations(mocks))
{
ExceptionThrowingClass exceptionThrowingClass = new
ExceptionThrowingClass();
exceptionThrowingClass.ExceptionThrowingMethod();
}
}
}
public class SetExpectations : IDisposable
{
private MockRepository mocks;
public SetExpectations(MockRepository mocks)
{
this.mocks = mocks;
}
public void Dispose()
{
mocks.ReplayAll();
}
}
public class ExceptionThrowingClass
{
public void ExceptionThrowingMethod()
{
throw new ApplicationException("exception thrown by class under test");
}
}
}
=========================
------ Test started: Assembly: ClassLibrary1.dll ------
TestCase 'Something.SomethingTest.ThrowExceptionInBlock' failed:
System.ApplicationException : exception thrown in test method
c:\projects\dotnet\solution1\classlibrary1\whatever.cs(23,0): at
Something.SomethingTest.ThrowExceptionInBlock()
TestCase 'Something.SomethingTest.ThrowExceptionFromMethodCall'
failed: System.ApplicationException : exception thrown by class under
test
c:\projects\dotnet\solution1\classlibrary1\whatever.cs(57,0): at
Something.ExceptionThrowingClass.ExceptionThrowingMethod()
c:\projects\dotnet\solution1\classlibrary1\whatever.cs(33,0): at
Something.SomethingTest.ThrowExceptionFromMethodCall()
0 succeeded, 2 failed, 0 skipped, took 0.03 seconds.
---------------------- Done ----------------------
==========================
This is exactly the feedback you would want. It points directly to the
lines throwing the exceptions in both cases.
So it's doable. The question is whether it's usable. Thoughts on that?
OK - I *think* this is what you mean - please tell me if I'm missing something:
====================================
using System;
using NUnit.Framework;
using Rhino.Mocks;
namespace Something
{
[TestFixture]
public class SomethingTest
{
MockRepository mocks;
Collaborator collaborator;
[SetUp]
public void SetUp()
{
mocks = new MockRepository();
collaborator = (Collaborator)mocks.CreateMock(typeof(Collaborator));
}
[Test] public void ReturnIsMissing()
{
using (new SetExpectations(mocks))
{
collaborator.SomeMethodThatReturnsBool();
}
}
[Test]
public void SecondMethodNotCalled()
{
using (new SetExpectations(mocks))
{
Expect.Call(collaborator.SomeMethodThatReturnsBool()).Return(true);
}
using (new VerifyExpectations(mocks))
{
throw new ApplicationException("thrown before expectation could be met");
}
}
}
public class SetExpectations : IDisposable
{
private MockRepository mocks;
public SetExpectations(MockRepository mocks)
{
this.mocks = mocks;
}
public void Dispose()
{
mocks.ReplayAll();
}
}
public class VerifyExpectations : IDisposable
{
private MockRepository mocks;
public VerifyExpectations(MockRepository mocks)
{
this.mocks = mocks;
}
public void Dispose()
{
mocks.VerifyAll();
}
}
public interface Collaborator
{
bool SomeMethodThatReturnsBool();
}
}
==============================================
------ Test started: Assembly: ClassLibrary1.dll ------
TestCase 'Something.SomethingTest.ReturnIsMissing' failed:
System.InvalidOperationException : Previous method
'Collaborator.SomeMethodThatReturnsBool();' require a return value or
an exception to throw.
at Rhino.Mocks.Impl.RecordMockState.PreviousMethodIsClose()
at Rhino.Mocks.Impl.RecordMockState.Replay()
at Rhino.Mocks.MockRepository.Replay(Object obj)
at Rhino.Mocks.MockRepository.ReplayAll()
c:\projects\dotnet\solution1\classlibrary1\whatever.cs(53,0): at
Something.SetExpectations.Dispose()
c:\projects\dotnet\solution1\classlibrary1\whatever.cs(24,0): at
Something.SomethingTest.ReturnIsMissing()
TestCase 'Something.SomethingTest.SecondMethodNotCalled' failed:
Rhino.Mocks.Exceptions.ExpectationViolationException :
Collaborator.SomeMethodThatReturnsBool(); Expected #1, Actual #0.
at Rhino.Mocks.Impl.ReplayMockState.Verify()
at Rhino.Mocks.MockRepository.Verify(Object obj)
at Rhino.Mocks.MockRepository.VerifyAll()
c:\projects\dotnet\solution1\classlibrary1\whatever.cs(68,0): at
Something.VerifyExpectations.Dispose()
c:\projects\dotnet\solution1\classlibrary1\whatever.cs(37,0): at
Something.SomethingTest.SecondMethodNotCalled()
0 succeeded, 2 failed, 0 skipped, took 0.11 seconds.
---------------------- Done ----------------------
'Collaborator.SomeMethodThatReturnsBool ();' require a return value or
an exception to throw.
at Rhino.Mocks.Impl.RecordMockState.PreviousMethodIsClose()
at Rhino.Mocks.Impl.RecordMockState.Replay()
at Rhino.Mocks.MockRepository.Replay (Object obj)
Cheers,
David