Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using MOQ to test a repository

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());
}
like image 282
Sam Avatar asked Oct 24 '13 23:10

Sam


People also ask

What is Moq testing what is its use?

Moq is a mocking framework built to facilitate the testing of components with dependencies. As shown earlier, dealing with dependencies could be cumbersome because it requires the creation of test doubles like fakes. Moq makes the creation of fakes redundant by using dynamically generated types.

Can we mock static methods using Moq?

You can use Moq to mock non-static methods but it cannot be used to mock static methods.

Is Moq a testing framework?

The Moq framework is an open source unit testing framework that works very well with . NET code and Phil shows us how to use it.

What can be mocked with Moq?

You can use Moq to create mock objects that simulate or mimic a real object. Moq can be used to mock both classes and interfaces.


1 Answers

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.

like image 65
Spock Avatar answered Sep 27 '22 21:09

Spock