爱猫的狗

拥抱变化

导航

Rhino 2006

http://www.ayende.com/Blog/CategoryView,category,My%20Projects%20,%20Rhino%20Mocks.aspx


Rhino Mocks Limitations

I'm getting a lot of questions recently about Rhino Mocks, and I wanted to address the common one here.

Rhino Mocks at the moment has two major limitations:

  • It cannot mock interfaces or classes with generic methods, like this one:

    public interface IFoo
    {
       public void Foo<T>();
    }

  • It cannot mock interfaces or classes with muli dimentional arrays, like this one:

    public interface IBar
    {
      public void Bar(string [,] strs);
    }

These two issues are known problems in Dynamic Proxy, both should be addressed in the next release of Dynamic Proxy (2.0), which is currently under work.

Beyond that, of course, there is the limitation of being able to mock only methods / properties / events that are marked as virtual. This is not something that is likely to change.


Rhino Mocks And Interfaces With Generics Methods

I the last week I was asked multiply times about using Rhino Mocks to mock interfaces with generic methods.

At the moment, this is not working, the code needed to generate generic methods on runtime is not trivial, and I tried fixing it several times, without success. Currently, Dynamic Proxy 2 is being worked on, which will have support for generic methods, so I expect to have this feature soon.

The following hack will get you by for now, it is not ideal, but it is what I have at the moment.

The ISession interface from NHibernate contains a generic Load<T>(object id) method, which measn that Rhino Mocks can't mock it. Here is how we solve the issue:

public abstract class SessionWithoutGenerics : ISession

{

       public override sealed Load<T>(object id)

       {

              return (T)Load(typeof(T), id);

       }

}

We basically disable the mocking of this specific method, and now we mock this class, and continue life as usual.

 


Advnace Mocking Scenarios With Active Record (using Rhino Mocks)

My recent post sparked some discussion in the Castle Mailing List, with some question of how to handle more complex scenarios. I managed to code those without much problem, as you can see below, but a few words first.

This method essentially mocks NHibernate for Active Record. This means that you have to understand what is happening (which is not a bad idea). I'm intimately familiar with both Active Record and NHibernate, and I had to look at the source to see what was happening.  If you intend to use this method, I highly recommend that you'll not put the expectations in the test itself, but move the expectation building to the MockScope, so you will be able to do something like:

mockedScope.OnFind(typeof(Blog),30).Return(mockedBlog);

Update: Added examples for both 2.0 and 1.1

Here are the examples (.Net 2.0):

[TestFixture]

public class Usage

{

    MockRepository mocks;

    ISession session;

 

    [TestFixtureSetUp]

    public void Initialize()

    {

        IConfigurationSource source = System.Configuration.ConfigurationManager.GetSection("activerecord") as IConfigurationSource; //this for .net 2.0

 

        ActiveRecordStarter.Initialize(typeof(Blog).Assembly, source);

    }

 

    [SetUp]

    public void SetUp()

    {

        mocks = new MockRepository();

        session = mocks.CreateMock<ISession>();

    }

 

    [TearDown]

    public void TearDown()

    {

        mocks.VerifyAll();

    }

 

    [Test]

    public void UsingMockedScope()

    {

        Blog mockedBlog = new Blog();

        Expect.Call(session.Load(typeof(Blog), 30)).Return(mockedBlog); ;

        mocks.ReplayAll();

        using (new MockScope(session))

        {

            Blog blogFromDatabase = Blog.Find(30);

            Assert.AreSame(mockedBlog, blogFromDatabase);

        }

    }

 

    [Test]

    public void UsingMockedScope_WithFindAll()

    {

        Blog mockedBlog = new Blog();

        IList returnedList = new ArrayList();

        returnedList.Add(mockedBlog);

        ICriteria criteria = mocks.CreateMock<ICriteria>();

        Expect.Call(session.CreateCriteria(typeof(Blog))).Return(criteria);

        Expect.Call(criteria.List()).Return(returnedList);

 

        mocks.ReplayAll();

 

        using (new MockScope(session))

        {

            Blog[] blogs = Blog.FindAll();

            Assert.AreEqual(1, blogs.Length);

            Assert.AreSame(mockedBlog, blogs[0]);

        }

    }

 

    [Test]

    public void UsingMockedScope_WithFindByProperty()

    {

        Blog mockedBlog = new Blog();

        IList returnedList = new ArrayList();

        returnedList.Add(mockedBlog);

 

        ICriteria criteria = mocks.CreateMock<ICriteria>();

        Expect.Call(session.CreateCriteria(typeof(Blog))).Return(criteria);

        Expect.Call(criteria.Add(null)).Constraints(

                Property.Value("Value", "Ayende @ Blog") & Property.Value("PropertyName", "Name")

            ).Return(criteria);

        Expect.Call(criteria.List()).Return(returnedList);

 

 

        mocks.ReplayAll();

 

        using (new MockScope(session))

        {

            Blog[] blogs = Blog.FindAllByProperty("Name", "Ayende @ Blog");

            Assert.AreEqual(1, blogs.Length);

            Assert.AreSame(mockedBlog, blogs[0]);

        }

    }

 

    [Test]

    public void UsingMockedScope_WithSimpleQuery()

    {

        Blog mockedBlog = new Blog();

        IList returnedList = new ArrayList();

        returnedList.Add(mockedBlog);

 

        IQuery query = mocks.CreateMock<IQuery>();

        Expect.Call(session.CreateQuery("from Blog b where b.Name = ?")).Return(query);

        Expect.Call(query.SetParameter(0, "Ayende @ Blog")).Return(query);

        Expect.Call(query.List()).Return(returnedList);

 

        mocks.ReplayAll();

 

        using (new MockScope(session))

        {

            SimpleQuery<Blog> q = new SimpleQuery<Blog>("from Blog b where b.Name = ?", "Ayende @ Blog");

            Blog[] blogs = q.Execute();

            Assert.AreEqual(1, blogs.Length);

            Assert.AreSame(mockedBlog, blogs[0]);

        }

    }

}

And here are the examples for .Net 1.1:

[TestFixture]

public class Usage

{

    MockRepository mocks;

    ISession session;

 

    [TestFixtureSetUp]

    public void Initialize()

    {

        IConfigurationSource source = System.Configuration.ConfigurationManager.GetSection("activerecord") as IConfigurationSource; //this for .net 2.0

 

        ActiveRecordStarter.Initialize(typeof(Blog).Assembly, source);

    }

 

    [SetUp]

    public void SetUp()

    {

        mocks = new MockRepository();

        session = (ISession)mocks.CreateMock(typeof(ISession));

    }

 

    [TearDown]

    public void TearDown()

    {

        mocks.VerifyAll();

    }

 

    [Test]

    public void UsingMockedScope()

    {

        Blog mockedBlog = new Blog();

        Expect.Call(session.Load(typeof(Blog), 30)).Return(mockedBlog); ;

        mocks.ReplayAll();

        using (new MockScope(session))

        {

            Blog blogFromDatabase = Blog.Find(30);

            Assert.AreSame(mockedBlog, blogFromDatabase);

        }

    }

 

    [Test]

    public void UsingMockedScope_WithFindAll()

    {

        Blog mockedBlog = new Blog();

        IList returnedList = new ArrayList();

        returnedList.Add(mockedBlog);

        ICriteria criteria = (ICriteria)mocks.CreateMock(typeof(ICriteria));

        Expect.Call(session.CreateCriteria(typeof(Blog))).Return(criteria);

        Expect.Call(criteria.List()).Return(returnedList);

 

        mocks.ReplayAll();

 

        using (new MockScope(session))

        {

            Blog[] blogs = Blog.FindAll();

            Assert.AreEqual(1, blogs.Length);

            Assert.AreSame(mockedBlog, blogs[0]);

        }

    }

 

    [Test]

    public void UsingMockedScope_WithFindByProperty()

    {

        Blog mockedBlog = new Blog();

        IList returnedList = new ArrayList();

        returnedList.Add(mockedBlog);

 

        ICriteria criteria = (ICriteria) mocks.CreateMock(typeof(ICriteria));

        Expect.Call(session.CreateCriteria(typeof(Blog))).Return(criteria);

        Expect.Call(criteria.Add(null)).Constraints(

                Property.Value("Value", "Ayende @ Blog") & Property.Value("PropertyName", "Name")

            ).Return(criteria);

        Expect.Call(criteria.List()).Return(returnedList);

 

 

        mocks.ReplayAll();

 

        using (new MockScope(session))

        {

            Blog[] blogs = Blog.FindAllByProperty("Name", "Ayende @ Blog");

            Assert.AreEqual(1, blogs.Length);

            Assert.AreSame(mockedBlog, blogs[0]);

        }

    }

 

    [Test]

    public void UsingMockedScope_WithSimpleQuery()

    {

        Blog mockedBlog = new Blog();

        IList returnedList = new ArrayList();

        returnedList.Add(mockedBlog);

 

        IQuery query = (IQuery)mocks.CreateMock(typeof(IQuery));

        Expect.Call(session.CreateQuery("from Blog b where b.Name = ?")).Return(query);

        Expect.Call(query.SetParameter(0, "Ayende @ Blog")).Return(query);

        Expect.Call(query.List()).Return(returnedList);

 

        mocks.ReplayAll();

 

        using (new MockScope(session))

        {

            IActiveRecordQuery q = new SimpleQuery(typeof(Blog),"from Blog b where b.Name = ?", "Ayende @ Blog");

            Blog[] blogs = (Blog[])q.Execute(session);

            Assert.AreEqual(1, blogs.Length);

            Assert.AreSame(mockedBlog, blogs[0]);

        }

    }

}


Rhino Mocks 2.7: Events

I finally got rid of the coding block, and I think I compensated by a large degree. Rhino Mocks has a new and improve version, including:

  • Support for raising events
  • Improved support for remoting mocks
  • Fixed a bug with regard to interfaces with the same name on different namespaces.

Check out the events:

IEventRaiser raiser;

 

[Test]

public void RaiseEvent()

{

    IWithEvents eventHolder = (IWithEvents)mocks.CreateMock(typeof(IWithEvents));

    eventHolder.Blah += null;

    LastCall.IgnoreArguments();

    raiser = LastCall.GetEventRaiser();

    eventHolder.RaiseEvent();

    LastCall.Do(new System.Threading.ThreadStart(UseEventRaiser));

   

    IEventSubscriber eventSubscriber = (IEventSubscriber)mocks.CreateMock(typeof(IEventSubscriber));

    eventSubscriber.Hanlder(this, EventArgs.Empty);

   

    mocks.ReplayAll();

 

    eventHolder.Blah += eventSubscriber.Hanlder;

    eventHolder.RaiseEvent();   

    mocks.VerifyAll();

}

 

private void UseEventRaiser()

{

    raiser.Raise(this, EventArgs.Empty);

}

The eventHolder.Blah += null syntax is something that I am forced to do because of the language constraint (C# doesn't allow to do anything to an event except subscribe / un-subscribe to it).

You can check it out here (sources and binaries, of course).


Rhino Mocks 2.5.5: Mocking Delegates

A brand new release of Rhino Mocks, with a twist.

Jeff emailed me a patch yesterday, that enabled the mocking of delegates. This is not something that I would've ever considered, to tell you the truth, but the idea (and the code) were sound, and I can certainly see uses for this. Consider all the possibilities for near-functional programming that .Net 2.0 opens for us, and you'll see what I'm excited about.

Here is an example of using this new functionality:

[Test]
public void GenericDelegate()
{
  Action<int> action = mocks.CreateMock<Action<int>>();
  for (int i = 0; i < 10; i++)
  {
    action(i);
  }
  mocks.ReplayAll();
  ForEachFromZeroToNine(action);
  mocks.VerifyAll();
}
private void ForEachFromZeroToNine(Action<int> act)
{
  for (int i = 0; i < 10; i++)
  {
    act(i);
  }
}

The other big change is removing IDisposable from the MockRepository. This is a breaking change for somet people, but it is a must. The problem is that the #1 problem that we have with new users of Rhino Mocks is exceptions masking. Removing the IDisposable means that it takes a bit longer to setup the mock repository, but I hope it will also encourage the recommended use of putting the setup & verification in the [SetUp] and [TearDown] methods.

Another change that I made was a bug fix for SetupResult not respecting IgnoreParameters(), which drove me crazy for a while. I’ve updated the documentation and the API docs to reflect the changes . As usualy, you can download or get the source.


Rhino Mocks 2.5.3: Out & Ref

This time I actually did nothing for this release. Jeff Brown added support for Out & Ref parameters for Dynamic Proxy, and all I had left to do is hit the build button a couple of times and upload the stuff to the server.

This closes the next to last big problem with Rhino Mocks. The very last one has to do with generic methods, which I hope to be able to fix this week.

 You know the drill, you can download it, or access the source directly.

posted on 2006-07-24 10:03  anf  阅读(317)  评论(0编辑  收藏  举报