0

I am on learning unit testing and have a question.

I mocked my class ApplicationDbContext with my data.

Now I would like to delete an item from my data. :(

The first Test passed but the second failed it still shows me 20 items

Can anyone help me?

My classe IApplicationDbContext :

    public interface IApplicationDbContext : IDisposable
{
    IDbSet<OrderingEquipment> OrderingEquipments { get; set; }
    int SaveChanges();
    Task<int> SaveChangesAsync();
}

My classe ApplicationDbContext:

    public class ApplicationDbContext : DbContext, IApplicationDbContext
{
    public virtual IDbSet<OrderingEquipment> OrderingEquipments { get; set; }

    public ApplicationDbContext() : base("DefaultConnection")
    {
    }
}

My classe OrderingEquipment:

    public class OrderingEquipment
{
    [Key]
    public Guid Guid { get; set; }
    public string Text { get; set; }
    public byte?[] xDEHFile { get; set; }
    public string xDEHFileName { get; set; }
    public DateTime Timestamp { get; set; }

    public string ModelToString()
    {
        return string.Format("Guid: {0}, Text: {1}, xDEHFile: {2}, xDEHFileName: {3} Timestamp: {4}", Guid, Text, xDEHFile, xDEHFileName, Timestamp);
    }
}

My Controller Methode:

public void Remove(OrderingEquipment orderingEquipments)
    {
        if (orderingEquipments == null)
        {
            //throw exception oder ein Result?
            throw new ArgumentNullException("orderingEquipmentsCointener is null");
        }

        try
        {
            using (db)
            {
                //orderingEquipments.ForEach(x => log.Error("Removed: " + x.ModelToString()));
                var itemToRemove = db.OrderingEquipments.Find(orderingEquipments.Guid);
                db.OrderingEquipments.Remove(itemToRemove);
                db.SaveChanges();
            }
        }
        catch (Exception e)
        {
           // orderingEquipments.ForEach(x => log.Error(x.ModelToString(), e));
        }
    }

My Unit Tests

public class OrderingEquipmentControllerTests
{

    private IDbSet<OrderingEquipment> dbSet;
    private IApplicationDbContext dbContext;
    private OrderingEquipmentController controller;

    [SetUp]
    public void Initialize()
    {
        // Create test product data
        var orderingEquipments = Builder<OrderingEquipment>.CreateListOfSize(20)
            .All()
            .With(p => p.Guid = Guid.NewGuid())
            .With(p => p.Timestamp = DateTime.UtcNow.AddMonths(new Random().Next(1)))
            .With(p => p.Timestamp = DateTime.UtcNow.AddDays(new Random().Next(2)))
            .TheFirst(5)
            .With(p => p.Text = "First five")
            .Build()
            .AsQueryable();

        dbSet = Substitute.For<IDbSet<OrderingEquipment>>();
        dbSet.Provider.Returns(orderingEquipments.Provider);
        dbSet.Expression.Returns(orderingEquipments.Expression);
        dbSet.ElementType.Returns(orderingEquipments.ElementType);
        dbSet.GetEnumerator().Returns(orderingEquipments.GetEnumerator());
        dbSet.Find(Arg.Any<object[]>()).Returns(callinfo =>
        {
            object[] idValues = callinfo.Arg<object[]>();
            if (idValues != null && idValues.Length == 1)
            {
                Guid requestedId = (Guid)idValues[0];
                return orderingEquipments.FirstOrDefault(p => p.Guid == requestedId);
            }

            return null;
        });

        dbContext = Substitute.For<IApplicationDbContext>();
        dbContext.OrderingEquipments.Returns(dbSet);

        controller = new OrderingEquipmentController(dbContext);
    }

    [Test()]
    public void GetAllOrderingEquipmentsTest()
    {
        var items = controller.GetAllOrderingEquipments();

        Assert.That(items.Count, Is.EqualTo(20));
    }

    [Test()]
    public void RemoveOneOrderingEquipment()
    {
        var items = controller.GetAllOrderingEquipments();
        controller.Remove(items.First());


        Assert.That(items.Count, Is.EqualTo(19));
    }
}
  • The mocked db is being backed by a fake data store used for querying. it is not actually saving anything added to it. You will have to also setup how the mock is suppose to be have when items are removed from it. – Nkosi Feb 02 '18 at 10:27
  • Can you explain it to me in more detail with an example please? – Dogus Sunna Feb 02 '18 at 10:30
  • Do not mock `DbContext` or you will end up with very complicated setup for every test, which in addition will tightly depend on implementation details. Which mean, that any refactoring (changing code without changing behaviour) will force you to change tests too. Instead use actual `DbContext` with Sqlite or if you are on "dotnet" use InMemory DbContext – Fabio Feb 09 '18 at 00:06

1 Answers1

0

The mocked db is being backed by a fake data store used for querying. it is not actually saving anything added to it. You will have to also setup how the mock is suppose to be have when items are removed from it.

// Create test product data
var orderingEquipments = Builder<OrderingEquipment>.CreateListOfSize(20)
    .All()
    .With(p => p.Guid = Guid.NewGuid())
    .With(p => p.Timestamp = DateTime.UtcNow.AddMonths(new Random().Next(1)))
    .With(p => p.Timestamp = DateTime.UtcNow.AddDays(new Random().Next(2)))
    .TheFirst(5)
    .With(p => p.Text = "First five")
    .Build()
    .ToList();

var queryable = orderingEquipments.AsQueryable();

dbSet = Substitute.For<IDbSet<OrderingEquipment>>();
dbSet.Provider.Returns(queryable.Provider);
dbSet.Expression.Returns(queryable.Expression);
dbSet.ElementType.Returns(queryable.ElementType);
dbSet.GetEnumerator().Returns(queryable.GetEnumerator());
dbSet.Find(Arg.Any<object[]>()).Returns(callinfo => {
    object[] idValues = callinfo.Arg<object[]>();
    if (idValues != null && idValues.Length == 1) {
        Guid requestedId = (Guid)idValues[0];
        return queryable.FirstOrDefault(p => p.Guid == requestedId);
    }
    return null;
});
//Setup remove on db set to remove actual item from fake collection
dbSet.Remove(Arg.Any<OrderingEquipment>())
    .Returns(callInfo => {
        var item = callInfo.Arg<OrderingEquipment>();
        return item != null && orderingEquipments.Remove(item);
    });

That way the mocked dbSet will know how to behave when it is asked to remove an item. Any members being used by the mock while testing has to be configured to behave as expected.

With that out of the way I must say that you should not be mocking code you do no own. DbSet does not need to be tested. MS would have tested it before release. You should also not have to use it directly in your controller. Abstract it out and have the abstraction injected into the controller. Allows greater flexibility when maintaining and testing your code.

Nkosi
  • 235,767
  • 35
  • 427
  • 472