Unit Testing and Mocking using RhinoMocks

2019-02-20 16:05发布

I am trying to setup tests for my new projects and come across some difficulties.

I am using NUnit and Rhino Mocks.

The Code that I am trying to test is this,

public DocumentDto SaveDocument(DocumentDto documentDto)
{
    Document document = null;
    using (_documentRepository.DbContext.BeginTransaction())
    {
        try
        {
            if (documentDto.IsDirty)
            {
                if (documentDto.Id == 0)
                {
                    document = CreateNewDocument(documentDto);
                }
                else if (documentDto.Id > 0)
                {
                    document = ChangeExistingDocument(documentDto);
                }

                document = _documentRepository.SaveOrUpdate(document);
                _documentRepository.DbContext.CommitChanges();
            }
        }            
        catch
        {
            _documentRepository.DbContext.RollbackTransaction();
            throw;
        }
    }
    return MapperFactory.GetDocumentDto(document);
}

And my testing code is as follows

[Test]
public void SaveDocumentsWithNewDocumentWillReturnTheSame()
{
    //Arrange

    IDocumentService documentService = new DocumentService(_ducumentMockRepository,
            _identityOfSealMockRepository, _customsOfficeOfTransitMockRepository,
            _accountMockRepository, _documentGuaranteeMockRepository,
            _guaranteeMockRepository, _goodsPositionMockRepository);
    var documentDto = new NctsDepartureNoDto();
    documentDto.IsDirty = true;
    documentDto.Id = 0;
    //Act
    var retDocumentDto = documentService.SaveDocument(documentDto);

    //Assert
    Assert.AreEqual(documentDto, documentDto);
}

private static IDbContext CreateMockDbContext()
{
    var dbContext = MockRepository.GenerateMock<IDbContext>();

    // setup expectations for DbContext mock
    //dbContextMock.Expect(...)
    // bind mock of the DbContext to property of repository.DbContext
    _ducumentMockRepository.Expect(mock => mock.DbContext).Return(dbContext).Repeat.Any();


    return dbContext;
}

I need to pass in a documentDto with say isDirty set and test if it returns the same object.

So I was thinking to use a Stub instead of a mock.

I need to to find out how to set expectations so I can test the logic on the code.

1条回答
一纸荒年 Trace。
2楼-- · 2019-02-20 16:26

you need to mock or stub all of the components which you do not want to test. You should, as a rule of thumb only have a maximum of single mock object the rest should be stubs. Mock the things you want to verify interaction with and stub the things which you just want to provide data for your test.

you don't tell us what type your _documentRepository is so its hard to tell exactly what you are testing here, but to test this method the only thing you can do, IMHO, is check that if the IsDirty flag is set is check that the correct methods on the _documentRepository and the Context are called.

To do this I would create a mock _documentRepository and mock DbContext and set expectations that _documentRepository.SaveOrUpdate(document) is called with the document passed in. Actually looking again at the code you need to convert between the dto and the document. Currently this is being done in a method. I would create a interface and a class for this and make that interface a dependency of the class you are testing so that you can create a stub which returns a known document from the documentDto. This class could handle creating a new document or returning an existing one based on the id in the Dto. otherwise you'll have to know what type of document is returned.

something like:

var documentDto = new NctsDepartureNoDto();
documentDto.IsDirty = true;
documentDto.Id = 0;

IDbContext context = MockRepository.GenerateMock<IDbRepository>();  
context.Expect(x=>x.BeginTransaction()).Return(MockRepository.GenerateStub<ITransaction>());
context.Expect(x=>x.CommitChanges());

then create a mock for the repository

IDocumentRepository repo = MockRepository.GenerateMock<IDocumentRepository>();
repo.Expect(x=>x.DbContext).Return(context).Repeat().Any();
repo.Expect(x=>x.SaveOrUpdate(Arg<Document>.Is.Any())).Return(MockRepository.GenerateStub<Document>);

This tests that you interact with the repository object correctly when the dirty flag is set. It shouldn't test that the document is saved correctly or that the correct document is returned when SaveOrUpdate is called, as this should be tested in the tests for the repository, not here.

'But wait!' I hear you cry, 'you said at the beginning that there should only be a single mock, and here we have 2!'. That's true, and I think that this shows a fault in your current design.

You should not, I don't think, be exposing the DBContext from your documentRepository. You seem to be doing so in order to use the transactions.

If your repository needs to be aware of the transactions then have methods on the repository that allow the transactions to be controlled (or hide the fact that the transactions exist inside the repository object completely). These methods might just delegate to the internal DbContext but it would then mean that the only mock would need to be the document repository object itself, and not the DbContext

查看更多
登录 后发表回答