So we were having a discussion in our team, over which testing frameworks people had used and which they thought were best. I went on to do a bit of research and there were a variety of articles out there leaning heavily in one way or the other.
So I decided to do a quick test myself, to see if I found one better that the other.
To do this, I looked at MSTest vs xUnit test frameworks, and Moq / nSubstitute for mocking dependencies. I had previously written some unit tests for an ImageTaxonomy class in this article using nSubstitute and xUnit. So to make my comprison, I set out to write the same set of tests using MSTest and Moq (which you can see at the bottom of this post).
A few of the differences can be seen below:
MSTest vs xUnit
Both are very similar in the small amount of examples I tried.
Test Method Attribute
MSTest:
[TestMethod]
xUnit:
[Fact]
Assertions
MSTest:
var ex = Assert.ThrowsException<ArgumentNullException>(sutAction);
Assert.AreEqual("mediaManager", ex.ParamName);
Assert.AreEqual(String.Empty, results);
xUnit:
var ex = Xunit.Assert.Throws<ArgumentNullException>(sutAction);
Assert.Equal("mediaManager", ex.ParamName);
Assert.Empty(results);
MSTest doesnt have an Empty Assert option.
Moq/nSubstitute
Creating the Mock
Moq:
var field = new Mock<Field>(ID.NewID, item);
nSubstitute:
var field = Substitute.For<Field>(ID.NewID, item);
When you want to use object in Moq you need to do:
field.Object
Properties
Moq:
field.SetupGet(x => x.Value).Returns(fieldValue);
nSubstitute:
field.Value = fieldValue;
Methods
Moq:
mediaManager.Setup(_ => _.GetMediaUrl(It.IsAny<MediaItem>())).Returns(imageUrl);
nSubstitute:
mediaManager.GetMediaUrl(Arg.Any<MediaItem>()).Returns(imageUrl);
The reality is they are both very similar to use, I didnt come across one thing one could do but the other couldnt. Neither is particularly difficult to use, especially if there exists some examples already in a project you have been asked to work on. I think people should just use what they want, then if you pick up a project to work on, just continue using the same as which has been adopted already.
Conclusion
My preference was nSubstitute, as it looks cleaner.
For comparison, here is my ImageTaxonomy class being tested using MSTest and Moq:
[TestClass]
public class ImageTaxonomyTests
{
private string mediaItemIDString = "12345678-1234-1234-1234-123456789012";
private ID mediaItemID = new ID("12345678-1234-1234-1234-123456789012");
[TestMethod]
public void Ctor_MediaManagerIsNull_Throws()
{
// arrange
Action sutAction = () => new ImageTaxonomy(null);
// act, assert
var ex = Assert.ThrowsException<ArgumentNullException>(sutAction);
Assert.AreEqual("mediaManager", ex.ParamName);
}
[TestMethod]
public void GetImageUrl_ItemIsNull_ReturnsEmpty()
{
// arrange
var mediaManager = new Mock<BaseMediaManager>();
var imageUrl = "~/Test-Image";
mediaManager.Setup(_ => _.GetMediaUrl(It.IsAny<MediaItem>())).Returns(imageUrl);
var sut = new ImageTaxonomy(mediaManager.Object);
// act
var results = sut.GetImageUrl(null, "Some Field");
// assert
Assert.AreEqual(String.Empty, results);
}
[TestMethod]
public void GetImageUrl_FieldNameIsNull_ReturnsEmpty()
{
// arrange
var database = new Mock<Database>();
var mediaManager = new Mock<BaseMediaManager>();
var item = CreateItem(database.Object);
var sut = new ImageTaxonomy(mediaManager.Object);
// act
var results = sut.GetImageUrl(item.Object, null);
// assert
Assert.AreEqual(String.Empty, results);
}
[TestMethod]
public void GetImageUrl_UnknownFieldName_ReturnsEmpty()
{
// arrange
var database = new Mock<Database>();
var mediaManager = new Mock<BaseMediaManager>();
var item = CreateItem(database.Object);
var sut = new ImageTaxonomy(mediaManager.Object);
// act
var results = sut.GetImageUrl(item.Object, "Some Unknown Field");
// assert
Assert.AreEqual(String.Empty, results);
}
[TestMethod]
public void GetImageUrl_KnownFieldName_ReturnsValue()
{
// arrange
var database = new Mock<Database>();
var item = CreateItem(database.Object);
SetItemField(item, "Some Known Field", $"<image mediaid='{mediaItemIDString}' />");
var mediaItem = CreateMediaItem(database.Object);
database.Setup(_ => _.GetItem(mediaItemID, It.IsAny<Language>(), Sitecore.Data.Version.Latest)).Returns(mediaItem);
var mediaManager = new Mock<BaseMediaManager>();
mediaManager.Setup(_ => _.GetMediaUrl(It.Is<MediaItem>(mi => mi.ID == mediaItem.ID))).Returns("/a/path/to/an/image.jpg");
var sut = new ImageTaxonomy(mediaManager.Object);
// act
var results = sut.GetImageUrl(item.Object, "Some Known Field");
// assert
Assert.AreEqual("/a/path/to/an/image.jpg", results);
}
private Mock<Item> CreateItem(Database database = null)
{
var item = new Mock<Item>(ID.NewID, ItemData.Empty, database);
var fields = new Mock<FieldCollection>(item.Object);
item.SetupGet(x => x.Fields).Returns(fields.Object);
return item;
}
private Item CreateMediaItem(Database database = null)
{
var definition = new ItemDefinition(mediaItemID, "Mock Media Item", ID.Null, ID.Null);
var data = new ItemData(definition, Language.Current, Sitecore.Data.Version.First, new FieldList());
var mediaItem = new Item(mediaItemID, data, database);
return mediaItem;
}
private void SetItemField(Mock<Item> item, string fieldName, string fieldValue)
{
var field = new Mock<Field>(ID.NewID, item);
field.SetupGet(x => x.Value).Returns(fieldValue);
field.SetupGet(x => x.Database).Returns(item.Object.Database);
//item.Fields[fieldName].Returns(field);
item.SetupGet(x => x.Fields[fieldName]).Returns(field.Object);
}
}