Ayende @ Rahien

It's a girl

Rhino Mocks - Arrange, Act, Assert Syntax

I intend to release the new version of Rhino Mocks soon, and I wanted to show the new syntax that I have been working on. I still need to write more thorough documentation, but I think that just the syntax should be a pretty good indication of how things are going.

Without further ado, here is the code:

[Test]
public void WhenUserForgetPasswordWillSendNotification_WithConstraints()
{
    var userRepository = MockRepository.GenerateStub<IUserRepository>();
    var notificationSender = MockRepository.GenerateStub<INotificationSender>();

    userRepository.Stub(x => x.GetUserById(5)).Return(new User { Id = 5, Name = "ayende" });

    new LoginController(userRepository, notificationSender).ForgotMyPassword(5);

    notificationSender.AssertWasCalled(x => x.Send(null),
        options => options.Constraints(Text.StartsWith("Changed")));
}

[Test]
public void WhenUserForgetPasswordWillSendNotification_WithArgMatchingInTheLambda()
{
    var userRepository = MockRepository.GenerateStub<IUserRepository>();
    var notificationSender = MockRepository.GenerateStub<INotificationSender>();

    userRepository.Stub(x => x.GetUserById(5)).Return(new User { Id = 5, Name = "ayende" });

    new LoginController(userRepository, notificationSender).ForgotMyPassword(5);

    notificationSender.AssertWasCalled(x => x.Send(Arg<string>.Matches(s => s.StartsWith("Changed"))));
}

[Test]
public void WhenUserForgetPasswordWillSendNotification_WithArgumentMatching()
{
    var userRepository = MockRepository.GenerateStub<IUserRepository>();
    var notificationSender = MockRepository.GenerateStub<INotificationSender>();

    userRepository.Stub(x => x.GetUserById(5)).Return(new User { Id = 5, Name = "ayende" });

    new LoginController(userRepository, notificationSender).ForgotMyPassword(5);

    notificationSender.AssertWasCalled(x => x.Send("Changed password for ayende"));
}


[Test]
public void WhenUserForgetPasswordWillSendNotification_UsingExpect()
{
    var userRepository = MockRepository.GenerateStub<IUserRepository>();
    var notificationSender = MockRepository.GenerateMock<INotificationSender>();

    userRepository.Stub(x => x.GetUserById(5)).Return(new User { Id = 5, Name = "ayende" });
    notificationSender.Expect(x => x.Send(null)).Constraints(Text.StartsWith("Changed"));

    new LoginController(userRepository, notificationSender).ForgotMyPassword(5);

    notificationSender.VerifyAllExpectations();
}

The class under test is:

public class LoginController
{
    private readonly IUserRepository repository;
    private readonly INotificationSender sender;

    public LoginController(IUserRepository repository, INotificationSender sender)
    {
        this.repository = repository;
        this.sender = sender;
    }

    public void ForgotMyPassword(int userId)
    {
        User user = repository.GetUserById(userId);
        sender.Send("Changed password for " + user.Name);
    }
}

Comments

Jeremy Gray
05/16/2008 02:13 PM by
Jeremy Gray

When it comes to more advanced argument validation I find myself waffling back and forth between the pros and cons of the WithConstraints and WithArgMatchingInTheLambda options so it is nice to see that both options will (?) be available.

Everything else is looking very nice, as much as I still have yet to be convinced of the merit of act/arrange/assert over the classic set-expectations/exercise/assert(&verify) style, except perhaps for the simplicity of the very phrase "act/arrange/assert". :)

John Chapman
05/17/2008 02:48 PM by
John Chapman

Ayende,

Awesome work. The AssertWasCalled looks to be a godsend for me. I've always wanted a way to verify just a single expectation at a time on a given mock. It would allow me to use a single set up and then many tests with only a single expectation check, versus when there are are two expectations on a mock and now you need to verify both on the mock. If there is another way now I would love to hear about it, for now I just look forward to your next release.

Good work!

For clarification, will both syntax options be available in the new release? The above syntax as well as the old legacy syntax, or will everything need to move over? I could see that causing some real issues for me as our project has a significant number of tests making sue of Rhino Mocks already.

Ayende Rahien
05/17/2008 04:11 PM by
Ayende Rahien

John,

Yes, we are going to remain backward compatible with the previous versions.

Jimmy Bogard
05/19/2008 04:31 PM by
Jimmy Bogard

I think this constraint syntax is finally viable. It always bothered me to have my constraints mixed in with my setup code. I want my assertions to be as clear as possible.

Looking good...though the name "GenerateStub" seems strange to me. Here's hoping we all simultaneously and spontaneously adapt Meszaros' test double nomenclature.

Stanislav Dvoychenko
05/19/2008 08:59 PM by
Stanislav Dvoychenko

Will it be possible to have something like:

factory.StubAny((f) => f.CreateParameter()).Return(MockRepository.GenerateStub());

The intent here would be to Stub Any call with this Return. Right now it looks like it is required to call Stub as many times as code under test will call it ...

Ayende Rahien
05/19/2008 09:03 PM by
Ayende Rahien

Depends on what you are trying to achieve.

This would return the same instance for all calls.

This is probably what you are trying to get:

factory.Stub((f) =>f.CreateParameter()).Repeat.Any.Do( () => MockRepository.GenerateStub());

Neil Mosafi
05/19/2008 09:51 PM by
Neil Mosafi

Great syntax, I love it!

Carel Lotz
05/22/2008 07:46 AM by
Carel Lotz

Is it possible to assert that a property setter was called using the new syntax?

Ayende Rahien
05/22/2008 10:38 AM by
Ayende Rahien

mock.AssertWasCalled(x => x.Name = "blah");

Stanislav Dvoychenko
05/22/2008 07:22 PM by
Stanislav Dvoychenko

Do with Action would be nice to have, I'm trying the Beta but see only IMethodOptions Do(Delegate action); which can't convert from lambda expression (and from anonymous delegate as well I guess?). God, it will be great to have lambda expressions everwhere.

Onur Gumus
05/26/2008 12:28 PM by
Onur Gumus

How do I test events with this syntax. My old test case is as below (appliying Model View Presenter) Can you please guide me a little bit :

        var mocks = new MockRepository();

        var view = mocks.DynamicMock<ICBOTExchangeView>();

        var provider = mocks.DynamicMock<IExchangeProvider>();


                   SetupResult.For(view.CBOTExchangeRates).PropertyBehavior();


        var result = new List<BaseFinanceData>();

        result.Add(new BaseFinanceData(){ Sell=1.1M, Buy = 2.2M});



        view.CBOTExchangeRatesRequested += null; 

        var loadRaiser = LastCall.IgnoreArguments().GetEventRaiser();


        Expect.Call(provider.GetCBOTExchangeRates()).Return(result);

        mocks.ReplayAll();


        var presenter = new  CBOTExchangeRatePresenter();

        presenter.View = view;

        presenter.ExchangeProvider = provider;

        loadRaiser.Raise(view, EventArgs.Empty);




        Assert.AreEqual(1, view.CBOTExchangeRates.Count);
Onur Gumus
05/26/2008 12:58 PM by
Onur Gumus

After some thought here is my implementation:

var mocks = new MockRepository();

        var view = MockRepository.GenerateMock<ICBOTExchangeView>();

        var provider = MockRepository.GenerateStub<IExchangeProvider>();


        var result = new List<BaseFinanceData>();

        result.Add(new BaseFinanceData(){ Sell=1.1M, Buy = 2.2M});


        view.Expect(v => v.CBOTExchangeRates).PropertyBehavior();

        var loadRaiser = view.Expect(v => v.CBOTExchangeRatesRequested += null).IgnoreArguments().GetEventRaiser();


        provider.Stub(p => p.GetCBOTExchangeRates()).Return(result);


        var presenter = new SmartInvest.Presenter.CBOTExchangeRatePresenter();

        presenter.View = view;

        presenter.ExchangeProvider = provider;

        loadRaiser.Raise(view, EventArgs.Empty);


        view.VerifyAllExpectations();            

        Assert.AreEqual(1, view.CBOTExchangeRates.Count);
Brad Wilson
05/26/2008 07:23 PM by
Brad Wilson

I don't particularly like this:

userRepository.Stub(x => x.GetUserById(5)).Return(new User { Id = 5, Name = "ayende" })

Because it mixes together what could be considered an assertion (I expect them to ask for user ID 5) with the stubbing behavior (return this). As an alternative, I think I'd prefer:

userRepository.StubReturn(x => x.GetUserById, new User { Id = 5, Name = "ayende" })

And then add an assertion about things being called correctly to the bottom:

userRepository.AssertWasCalled(x => x.GetuserById(5))

Ayende Rahien
05/26/2008 09:34 PM by
Ayende Rahien

Stub() doesn't set an expectation.

However, what I have found is that very often you want to do things like:

userRepository.Stub(x => x.GetUserById(5)).Return(new User { Id = 5, Name = "ayende" })

userRepository.Stub(x => x.GetUserById(6)).Return(new User { Id = 6, Name = "brad" })

This is just matching to parameters.

You can ask to not do that, but it turn out to be a rarer case in most scenarios.

Jeremy Gray
06/10/2008 05:17 PM by
Jeremy Gray

@Ayende - I know this is now an old-ish post but thought it the best place to pose this question:

I've been trying out the AAA syntax in the last day and noticed that GenerateMock doesn't appear to produce strict mocks, nor can it take constructor arguments. On the other hand, instances returned from GenerateStub seem capable of taking on expectations, which given the now-looser semantics of GenerateMock would tempt me to suggest that the only difference between GenerateMock and GenerateStub should be that instances returned by GenerateStub shouldn't be able to take on any expectations. I've also noticed that there is no Generate* equivalent to PartialMock, though both GenerateStub and GenerateMock seem happy to produce one (not that I can tell the difference in expected behaviour due to the other things noted earlier.)

Could you perhaps post back a quick reply comment showing what maps to what between the between old the MockRepository instance methods and the new static GenerateMock and Generatestub methods? Thanks in advance!

Jeremy Gray
06/10/2008 05:22 PM by
Jeremy Gray

Hehe. Nice copy/paste editing error on my part in that last comment. The actual question from me to Ayende should read:

Could you perhaps post back a quick reply comment showing what maps to what between the old MockRepository instance methods and the new static GenerateMock and GenerateStub methods?

Ayende Rahien
06/14/2008 12:52 AM by
Ayende Rahien

The static methods are returning an object that can be used without record/replay. It is already in replay mode, which is needed to use the AAA syntax.

The missing partial is mainly an issue with not adding it, not a design decision.

I need to document that part fairly heavily

Jeremy Gray
06/14/2008 01:36 AM by
Jeremy Gray

Yes, Ayende, I understand that the objects can be used without explicit record/replay, and have been using them that way quite successfully for most of the week. What I am curious about, though, is that the two methods are returning objects that don't seem to align with the previous terminology. The Mocks, for example, can be used as partial mocks, and more importantly have changed their strictness (they are acting much more loose than those returned from the classic StrictMock calls). I understand that you have yet to document this new syntax fully, but could you at least post a quick list of old call versus new call so that we can know what is what?

Michael Reichenauer
06/25/2008 07:51 AM by
Michael Reichenauer

Hi!

Thanks for the new 3.5 beta syntax, it is really nice to use.

However I have problems with mocking properties. E.g.:

var httpClient = MockRepository.GenerateStub();

httpClient.BaseAddress = "blah";

httpClient.AssertWasCalled(x => x.BaseAddress = "blah");

Will cause a ExpectationViolationException on the third row. And also trying to mock properties properties with:

var httpClient = MockRepository.GenerateStub();

httpClient.Stub(m => m.BaseAddress).Return("mocked");

or:

httpClient.Stub(m => m.BaseAddress = null).Throw(new ArgumentException("error"));

fails on the Stub rows with a "InvalidOperationException : Invalid call, the last call has been used or no call has been made ..."

I would appreciate if someone could give me a hint.

Ayende Rahien
06/25/2008 11:58 AM by
Ayende Rahien

This should go to the mailing list

Morten Lyhr
07/09/2008 06:54 AM by
Morten Lyhr

I really like it alot.

I have written about BDD and AAA syntax here:

http://morten.lyhr.dk/2008/07/doing-bdd-with-rhino-mocks-aaa-syntax.html

They really mix very well.

Comments have been closed on this topic.