Using MOQ to test a repository

20,860

Your approach is good, however I would adjust few things. I have made few changes to your test and mocking components which you can see below.

Unit Test

You method under test within your Unit Test (see below), does not really match with the method which you have defined in your question.

Unit test method call:

SubmissionVersion.DeleteNote(repo.Object, subVersion.Object, note.Id.Value);

Method under test:

public static SubmissionVersion DeleteNote
  (IRepository repository, SubmissionVersion version, Guid noteId)

I assume the above method is part of another class, I called it as NotesHelper - Not the ideal name for repository calls, but it is just to get the compilation working.

I would personally separate out your Unit test into 2 separate Unit tests. One to verify whether the required methods being called, and the other is to verify whether the notes have been removed from the collection.

Also instead of creating a mocked SubmissionVersion, I created a fakeSubmissionVersion. This is because the routines within SubmissionVersion may not be mockable. You can also do a state based testing (preferred) to ensure the version.Notes.Remove(note); has been called.

[Fact]
public void DeleteNote_Deletion_VerifyExpectedMethodsInvokecOnlyOnce()
{
    // Arrange
    var note = new Note { Id = Guid.NewGuid() };
    var fakeSubmissionVersion = new SubmissionVersion() { Notes = new List<Note>() };
    var repo = new Mock<IRepository>();
    repo.Setup(x => x.GetById<Note>(It.IsAny<Guid>())).Returns(note);

    // Act
    NotesHelper.DeleteNote(repo.Object, fakeSubmissionVersion, note.Id.Value);

    // Assert
    repo.Verify(x => x.GetById<Note>(note.Id), Times.Once());
    repo.Verify(x => x.Save(fakeSubmissionVersion), Times.Once());
    repo.Verify(x => x.Delete(note), Times.Once());
}

The above test can be further improved by defining each Verification in its own test.

[Fact]
public void DeleteNote_Deletion_VerifyDeleteMethodCalledOnlyOnce()

This way if one the test fails we would know exactly which method has not been called with the expectation. Sometimes, having multiple verifications as above can be problematic. For example, if the Save method has not been called, you would never know the Delete method has been called or not. This is because the exception has been thrown when the Save method has not been called and the test execution has been terminated.

This would result in multiple tests but they are more readable and maintainable. Of course you can refactor the common code into a factory or helper method.

[Fact]
public void DeleteNote_RemoveNotes_ReturnsExpectedNotes()
{
    // Arrange
    var note = new Note { Id = Guid.NewGuid() };
    var fakeSubmissionVersion = new SubmissionVersion() { Notes = new List<Note>() { note } };
    var repo = new Mock<IRepository>();
    repo.Setup(x => x.GetById<Note>(It.IsAny<Guid>())).Returns(note);

    // Act
    NotesHelper.DeleteNote(repo.Object, fakeSubmissionVersion, note.Id.Value);

    // Assert
    Assert.AreEqual(0, fakeSubmissionVersion.Notes.Count);
}

Additional Note: Also providing a descriptive Unit test method names improves the readability of the Unit Tests.

Share:
20,860
Sam
Author by

Sam

.Net Developer with a strong emphasis on client side technologies.

Updated on March 05, 2020

Comments

  • Sam
    Sam about 4 years

    I am trying to test a repository using MOQ to mock the behavior of the repo. I am failry new to MOQ, so bear with me please.

    Given the following method:

    public static SubmissionVersion DeleteNote(IRepository repository, SubmissionVersion version, Guid noteId)
    {
        Note note = repository.GetById<Note>(noteId);
        version.Notes.Remove(note);
        repository.Save(version);
        repository.Delete(note);
        return repository.GetById<SubmissionVersion>(version.Id);
    }
    

    Does this test look OK?

    [Fact]
    public void DeleteNoteV2()
    {
        // Arrange
        var note = new Note{ Id = Guid.NewGuid()};
    
        var subVersion = new Mock<SubmissionVersion>();
        subVersion.Setup(x => x.Notes.Remove(note));
    
        var repo = new Mock<IRepository>();
        repo.Setup(x => x.GetById<Note>(note.Id)).Returns(note);
        repo.Setup(x => x.GetById<SubmissionVersion>(It.IsAny<Guid?>())).Returns(subVersion.Object);
    
        // Act
        SubmissionVersion.DeleteNote(repo.Object, subVersion.Object, note.Id.Value);
    
        // Assert
        repo.Verify(x => x.GetById<Note>(note.Id), Times.Once());
        repo.Verify(x => x.Save(subVersion.Object), Times.Once());
        repo.Verify(x => x.Delete(note), Times.Once());
    
        subVersion.Verify(x => x.Notes.Remove(It.IsAny<Note>()), Times.Once());
    }