Should always ignoreArgument when AssertWasCalled

I should have added this option in my test earlier, then I won’t waste 2+ hours today. The reason causing problem was very simple, AssertWasCalled() method in current verion RhinoMocks (3.5.0.1337 RC) doesn’t action consistently. I think it’s because comparing the object reference equalty. I had to add options=>options.IgnoreArguments() to get around from this.

Here is a piece of my MVP + CLSA test. Struggled for a while with Property setter, thanks God, it isn’t that hard.


    [TestFixture]
    public class When_initializing_view : EstablishContext
    {
        [Test]
        public void should_call_view_setter_if_id_exists()
        {
            // Arrange
            _mockOrganizationRepository.Expect(x => x.FindById(1)).Return(_stubOrganizationDTO);

            _mockView = MockRepository.GenerateMock<IEditDetailsView<OrganizationBO>>();

            _presenter = new EditDetailsPresenter<OrganizationBO>(_mockView);

            // Action
            _presenter.InitViewFor(1);

            // Assert
            _mockView.AssertWasCalled(x => x.CurrentBO = null,
                options =>options.IgnoreArguments() );
        }
    }

I then created a generic EditDetailsPresenter, worked as I expected with one concern left: How to mock DataPortal directly. Currently I am still mocking repository. Ideally I should mock DataPortal instead, otherwise I have to make sure there is at least one BO is fully functional.

Mocking DataPortal is very hard, because those Fecth, Create, Update and Delete are all static methods, I had to create an adapter to wrap those static methods, then using StructreMap again inside adapter enable switching between DataPortal and mock DomainDAO, here is what I have tried:

  1. Create DomainDAO and IDomainDAO,
    public class DomainDAO<BO> : IDomainDAO<BO>
    {
    public BO Fetch(object criteria)
    {
    return DataPortal.Fetch<BO>(criteria);
    }
    
    public BO Create()
    {
    return DataPortal.Create<BO>();
    }
    
    public BO Update(BO bo)
    {
    return DataPortal.Update<BO>(bo);
    }
    
    public void Delete(object criteria)
    {
    DataPortal.Delete<BO>(criteria);
    }
    }
    
    public interface IDomainDAO<T>
    {
    T Fetch(object criteria);
    T Create();
    T Update(T bo);
    void Delete(object criteria);
    }
    
    
  2. Create DAOManager to Get DomainDAO:
    public static class DAOManager
    {
    public static IDomainDAO<BO> DomainDAO<BO>()
    {
    return ObjectFactory.GetInstance<IDomainDAO<BO>>();
    }
    }
    
    
  3. Change classic factory method in BO from DataPortal to this new DAOManager.
    public static OrganizationBO GetOrganizationBO(int stakeholderId)
    {
    return DAOManager.DomainDAO<OrganizationBO>().Fetch(new SingleCriteria<OrganizationBO, int>(stakeholderId));
    //            return DataPortal.Fetch<OrganizationBO>(new SingleCriteria<OrganizationBO, int>(stakeholderId));
    }
    
  4. The presenter testing code can be changed to:
    
    [SetUp]
    public void SetUp()
    {
    _mockService = MockRepository.GenerateMock<IDomainDAO<OrganizationBO>>();
    ObjectFactory.Inject<IDomainDAO<OrganizationBO>>(_mockService);
    }
    
    [TearDown]
    public void TearDown()
    {
    ObjectFactory.ResetDefaults();
    }
    
    [TestFixture]
    public class When_initializing_view : EstablishContext
    {
            [Test]
            public void should_call_view_setter_if_id_exists()
            {
                // Arrange
                // We don't need to set return value here.
                _mockService.Expect(x => x.Fetch(new SingleCriteria<OrganizationBO, int>(68190)));
    
                _mockView = MockRepository.GenerateMock<IEditDetailsView<OrganizationBO>>();
    
                _presenter = new EditDetailsPresenter<OrganizationBO>(_mockView);
    
                // Action
                _presenter.InitViewFor(68190);
    
                // Assert
                _mockView.AssertWasCalled(x => x.CurrentBO = null,
                    options => options.IgnoreArguments() );
            }
    
            [Test]
            public void should_set_error_msg_in_view_if_id_not_exists()
            {
                // Arrange
                Exception ex = new Exception("Data Not Found", new DataNotFoundException());
                _mockService.Expect(x => x.Fetch(new SingleCriteria<OrganizationBO, int>(1)))
                    .IgnoreArguments()
                    .Throw(new DataPortalException("DataPortal.Fetch failed.",
                        ex,
                        null)); // See, we don't have to mock BO here, unless you try to read BO through exception in UI.
    
                _mockView = MockRepository.GenerateMock<IEditDetailsView<OrganizationBO>>();
    
                _presenter = new EditDetailsPresenter<OrganizationBO>(_mockView);
    
                // Action
                _presenter.InitViewFor(1);
    
                // Assert
                _mockView.AssertWasCalled(x => x.ShowError(null),
                    options => options.IgnoreArguments() );
            }
    

Fetch can be done this way, but I can’t mock Save() because it’s deep in Csla, unless I can override BusinessBase.Save() which is another mission impossible (too many internal classes involved). To make Csla TDD friendly, let ask for TypeMock to save us out.

 [TestFixture]
    public class When_view_firing_save_event : EstablishContext
    {
        [Test]
        public void should_set_success_msg_and_set_new_BO_in_view_if_save_new_bo_succeed()
        {
            // Arrange
            OrganizationBO orgBo = DataPortal.Create<OrganizationBO>();
            orgBo.Name = "test Org";
            Assert.That(orgBo.IsValid, orgBo.GetValidationMessage());

            _mockView = MockRepository.GenerateMock<IEditDetailsView<OrganizationBO>>();
            _mockView.Stub(x => x.CurrentBO).Return(orgBo).Repeat.Any();

            _presenter = new EditDetailsPresenter<OrganizationBO>(_mockView);

            // This won't work, because BusinessBase.Save() is tightly coupled to DataPortal.
            //_mockService.Expect(x => x.Update(orgBo)).IgnoreArguments();

            // Use TypeMock to mock DataPortal.Update() inside BusinessBase.Save()
            using (RecordExpectations recorder = RecorderManager.StartRecording())
            {
                  // CAUTION: ALL calls here are mocked!!!
                  recorder.DefaultBehavior.IgnoreArguments();
                  recorder.ExpectAndReturn(DataPortal.Update(orgBo), orgBo);
            }

            // Action
            _mockView.Raise(x => x.RequestSave += null, this, EventArgs.Empty);

            // Assert
            _mockView.AssertWasCalled(x => x.CurrentBO = null,
                                      options => options.IgnoreArguments());

            _mockView.AssertWasCalled(x => x.ShowMessage(null),
                                      options => options.IgnoreArguments());

    }

We can remove DomainDAO then if using TypeMock to mock DataPortal.

The new ObjectFactory in CSLA 3.6 seems can make this mock easier, looks like your own ObjectFactory is not static class anymore, different than DataPortal. By creating your own IDataPortal or IObjectFactory, as Ryan did,  mocking those service layer objects should become possible. Someday I will look into this CSLA.ObjectFactory thing if we aren’t going to TypeMock. Rocky’s statement indicates this will be a lot of work:

It is very important to realize that when using this factory model, the data portal does virtually nothing for you. It doesn’t automatically call MarkOld(), MarkNew() or MarkAsChild(). You assume total responsibility for creating and initializing the business object graph, and for setting the state of all objects in the graph.

But it seems Ryan’s ObjectFactroy sample code already finished most of them. One thing doesn’t fit our situation is, we need IRepository<> changed to IDomainObjectRepository in Factory.


        private readonly IRepository<T> _repository;
        public BusinessBaseServerFactory(IRepository<T> repository)
        {
            _repository = repository;
        }

So far, I think unless CSLA.ObjectFactory can provide an optional attribute something like “RepositoryType”, I have to create One ObjectFacotry per BO. Our libaray will become more messier.

Advertisements