I am using xUnit and Moq to write my unit tests, and I have a lot of duplicate code in my various tests that I would like to pull out into some reusable fashion.
Duplicate Code
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);
Given the following tests, how can I clean them up so there is not so much duplication?
[Fact]
public void Should_CallRepoGetNoteByIdOnce()
{
// 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());
}
[Fact]
public void Should_CallSubmissionVerionNotesRemoveOnce()
{
// 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
subVersion.Verify(x => x.Notes.Remove(note), Times.Once());
}
[Fact]
public void Should_CallRepoSaveSubmissionVersionOnce()
{
// 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.Save(subVersion.Object), Times.Once());
}
[Fact]
public void Should_CallRepoDeleteNoteOnce()
{
// 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.Delete(note), Times.Once());
}
[Fact]
public void Should_CallRepoGetSubmissionVersionByIdOnce()
{
// 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<SubmissionVersion>(subVersion.Object.Id), Times.Once());
}
[Fact]
public void Should_RemoveNotesFromSubmissionVersion()
{
// Arrange
var repo = new CompositeRepository().GenerateCompositeRepository<Guid?>(typeof(SubmissionVersion), typeof(Note));
var subVersion = new SubmissionVersion { Id = Guid.NewGuid() };
var note = new Note { Id = Guid.NewGuid(), Content = "Test Note" };
repo.Save(note);
subVersion.Notes.Add(note);
// Act
subVersion.Notes.ToList().ForEach(x => SubmissionVersion.DeleteNote(repo, subVersion, x.Id.Value));
// Assert
Assert.Null(repo.GetById<Note>(note.Id));
}
Any suggestions/patterns that are best practice?
I typically approach this by making a unit test context object that exposes reusable Mocks as public properties. The object will set up the common mocks internally and just expose them as public properties. You can potentially define many reusable mocks in this class.
Ex:
public class UnitTestContext
{
public Mock<IRepository> Repo {get;set;}
public UnitTestContext()
{
// create suitable note / subversion objects
// either by passing them in or new-ing them up directly with default values.
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);
}
}
The test can then create an instance:
[Fact]
public void Some_Test_In_Need_Of_A_Mocked_Repository()
{
var ctx = new UnitTestContext();
SubmissionVersion.DeleteNote(ctx.Repo.Object, subVersion.Object, note.Id.Value);
}
I prefer this approach over defining the mocks as members in the test class since the UnitTestContext is reusable across test classes.
If you need more flexibility when it comes to return values, you can also pass in objects to the context when you construct the mocks. You can also add to the mocks outside of the class through the Repo property.
In general, xUnit guys don't like the idea of test setups. But if you need to, you can expose some of the commonly used objects as private members of the test class, and use a parameter-less ctor to initialize. Read attribute comparisons with other testing frameworks.
private readonly Mock<IRepository> _testRepository;
private readonly Mock<SubmissionVersion> _submissionVersion;
private readonly Note _testNote;
public MyTestClass()
{
_submissionVersion = new Mock<SubmissionVersion>();
_testNote = new Note { Id = Guid.NewGuid() };
_testRepository = new Mock<IRepository>();
_testRepository.Setup(r => r.GetById<Note>(_testNote.Id).Returns(_testNote);
_testRepository.Setup(r => r.GetById<SubmissionVersion>(It.IsAny<Guid?>())).Returns(_submissionVersion.Object);
}
[Fact]
public void Should_CallSubmissionVerionNotesRemoveOnce()
{
// Arrange
// done is setup
// Act
SubmissionVersion.DeleteNote(_testRepository.Object, _submissionVersion.Object, note.Id.Value);
// Assert
_submissionVersion.Verify(x => x.Notes.Remove(note), Times.Once());
}
[Fact]
public void Should_CallRepoSaveSubmissionVersionOnce()
{
// Arrange
// in setup
// Act
SubmissionVersion.DeleteNote(repo.Object, subVersion.Object, note.Id.Value);
// Assert
_testRepository.Verify(r => r.Save(_submissionVersion.Object), Times.Once());
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With