使用RhinoMock

本文介绍了使用RhinoMock进行单元测试的技巧,包括Strict Mock、Dynamic Mock和Partial Mock的使用,以及Stub和Mock的区别。重点讲解了如何设置期望、忽略参数、处理事件和多接口支持。同时,提到了不能mock private成员,但可以mock internal成员,以及在测试中如何处理无参数函数和属性的期望设置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考:

Step by step mocking with RhinoMock | My Memory

Using Rhino.Mocks - quick guide to generating mocks and stubs - Musing, Rants & Jumbled Thoughts of John M. Wright

分三种类型:

  • Strict Mock:GenerateStrictMock<T>,被调用的函数必须需要实现(Expect或Stub)
  • Dynamic Mock:GenerateMock<T>, 没实现的部分,默认值返回。
  • Partial Mock:GeneratePartialMock<T>,除了被替换的函数,其他的都执行原来的类方法。
  • Stubs vs Mocks: 感觉两者差不多,Stub用Except(),Mock用Stub(),例如:_mockDAO.Stub().Return()
  • .Repeat.Any(), .Repeat.Once(), .Repeat.Times(10) 
_mockDAO.Stub(dao => dao.GetRecordFromDatabase(myRecordId))
             .Repeat.Any()
             .Return(recordFromDatabase);
  • IgnoreArguments():
  • 设定约束条件,例如如果参数>10,返回什么
  _mockDAO.Stub(dao => dao.GetRecordFromDatabase(Arg<int>.Is.GreaterThanOrEqual(0)))
               .Return(recordFromDatabase);
  • 参数里某个属性的值是xxx时,返回什么
    _mockDAO.Stub(x => x.SomeMethod(myObject))
                    .Constraints(Property.Value("IsSomethingICareAbout", true)
                    .Return("foo");
  • Mock属性
    [Test]
    public void TestProperty()
    {
        var mockedInterface = MockRepository.GenerateMock<IHasAProperty>();
        mockedInterface.Expect(x => x.StringProperty).Return("fooString");
        Assert.That(mockedInterface.StringProperty, Is.EqualTo("fooString"));
    }
  • 不用return,用Exception
  • 注:不能mock private私有或保护成员,但可以mock interal成员
  • 注文中里的BLL :a Business Logic Layer (C#) - Microsoft Learn

  • CreateMock创建Mock的时候,要求严格,被调用的函数必须需要实现(Expect或Stub)。而DynamicMock就不需要。
  • ​
    [Test]
    public void TestMock()
    {
       ISession session = repository.CreateMock<ISession>();
       Expect.Call(session.Subscribe(null)).IgnoreArguments().Return(null).Repeat.Any();
     
       repository.ReplayAll();
     
       Mapper mapper = new Mapper(session);
    }
    
    ​
  • Record/Playback (不是很清楚)

by default, in record mode we can simply start creating our expectations, 
then when we’re ready to act on those mocked objects we playback mode using repository.ReplayAll().

执行Expect时候,就开始播放了;当执行ReplayAll就开始回放了。
问题是会放以后才Action? => Mapper mapper = new Mapper(session);

  • 针对没有参数函数:void DoSomething()函数的Expect

正确:Expect.Call(session.DoSomething).Repeat.Any();
错误: Expect.Call(session.DoSomething()).Repeat.Any();

  • 针对属性public string Result { get; set; }的Expect

Expect.Call(session.Result).Return("hello").Repeat.Any();

  • 测试一个private函数的方法,该函数是被事件驱动(重要)

Mocking Event Handlers

LastCall: LastCall allows us to set our expectations on the previous method call.

[Test]
public void TestMock()
{
   ISession session = repository.CreateMock<ISession>();
 
   session.StatusChanged += null;
   LastCall.IgnoreArguments();
 
   IEventRaiser raiser = LastCall.GetEventRaiser();
 
   repository.ReplayAll();
 
   Mapper mapper = new Mapper(session);
   Assert.AreEqual("Connected", mapper.Status);
 
   raiser.Raise(null, null);
 
   Assert.AreEqual("Disconnected", mapper.Status);
}

Notice we use LastCall to create an expectation on the += being called on the StatusChanged event. This should run without any errors.
Notice we use the LastCall.GetEventRaiser() to get an IEventRaiser. This will allow us to raise events on the StatusChanged event.

  • 多接口支持,What are the *MultiMocks?
public Mapper(ISession session)
{
   Status = "Connected";
   this.session = session;
 
   IStatusUpdate status = session as IStatusUpdate;
   if (status != null)
   {
      status.StatusChanged += (sender, e) =>
      {
         Status = "Disconnected";
      };
   }
}

注意:IStatusUpdate status = session as IStatusUpdate;
支持写法: ISession session = repository.CreateMultiMock<ISession>(typeof(IStatusUpdate));

  • 支持class: PartialMock: 重要!!

  • Stubs,CCW最常用的方式

不关心是否被调用过,可以使用这个最简单的方式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值