1

My entities are like below:

public class Class1
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual Class2 Class2{ get; set; }
}

public class Class2
{
    [Key, ForeignKey("Class1")]
    public int Id { get; set; }
    public int? Price { get; set; }
    public virtual Class1 Class1{ get; set; }
}

And I have the following test that I expected to be failed but it is passed:

[TestMethod]
public void CreateClass2()
{
    var mockSet = new Mock<DbSet<Class2>>();

    var mockContext = new Mock<theContext>();
    mockContext.Setup(m => m.Class2s).Returns(mockSet.Object);

    var service = new Class2Service(mockContext.Object);
    service.AddClass2(10, 100);

    mockSet.Verify(m => m.Add(It.IsAny<Class2>()), Times.Once());
    mockContext.Verify(m => m.SaveChanges(), Times.Once());
}

And this is the service.AddClass2 method:

public class Class2Service
{
    private readonly theContext _context;

    public CoordService(theContext context)
    {
        _context = context;
    }
    public Class2 AddClass2(int id, int? price)
    {
        var class2 = _context.Class2s.Add(new Class2 { Id = id, Price = price });
        _context.SaveChanges();

        return class2;
    }
}

Since I have not added a Class1 yet and entered an invalid id for Class2 (i.e it is not exist in Class1), the new entry for Class2 should be invalid since I have constrained it with foreign key. In my project and real database it works as expected and gives me error, but in this test it is passed!

  • I think your ForeignKey("Class1") should be ForeignKey("Class1Id") – Steve May 09 '18 at 13:38
  • @Steve this `ForeignKey` behavior is working as expected in my database and real adding Class2, it just not working in the test. –  May 09 '18 at 13:40

1 Answers1

0

The test uses mock context instead of a real one, so _context.SaveChanges(); will actually do nothing, so no exception is thrown.

You can easily google on how to mock a DbContext properly, e.g. here or here

Also I think the unit test itself should be written in a different way. I.e. you are testing the method calls here, which is the underlying implementation of adding a record.

A proper unit test for this case would check if the service.AddClass2(10, 100); call throws exception. And a separate test should be written to see if a new record appears in the DbSet as a result of service.AddClass2 method call instead of checking that some particular methods were called.

UPD: an example of setting an IQueryable as a source for a fake context.

    public static class MockDbSetExtensions
{
    public static void SetSource<T>(this Mock<DbSet<T>> mockSet, IList<T> source) where T : class
    {
        var data = source.AsQueryable();
        var iQuryableSet = mockSet.As<IQueryable<T>>();

        iQuryableSet.SetupGet(m => m.Provider).Returns(data.Provider);
        iQuryableSet.SetupGet(m => m.Expression).Returns(data.Expression);
        iQuryableSet.SetupGet(m => m.ElementType).Returns(data.ElementType);
        iQuryableSet.Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());
    }
}

And sample usage:

    [TestClass]
public class GigRepositoryTests
{

    private GigRepository _repository;
    private Mock<DbSet<Gig>> _mockGigs;
    private Mock<IApplicationDbContext> mockContext;

    [TestInitialize]
    public void TestInitialize()
    {
        _mockGigs = new Mock<DbSet<Gig>>();

        mockContext = new Mock<IApplicationDbContext>();


        _repository = new GigRepository(mockContext.Object);
    }

    [TestMethod]
    public void GetUpcomingGigsByArtist_GigsInThePast_ShouldNotBeReturned()
    {
        var gig = new Gig() {DateTime = DateTime.Now.AddDays(-1), ArtistId = "1"};

        _mockGigs.SetSource(new List<Gig> {gig});
        mockContext.SetupGet(c => c.Gigs).Returns(_mockGigs.Object);

        var gigs = _repository.GetUpcomingGigs("1");

        gigs.Should().BeEmpty();
    } }
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Andrew K
  • 263
  • 1
  • 8
  • I have exactly used this reference https://msdn.microsoft.com/en-us/library/dn314429(v=vs.113).aspx as you linked, but I don't know how should I mock a DbContext property? Also theContext that is used in this test is used the `Class2s` that is the real one used in my original DbContext. –  May 09 '18 at 13:58
  • Can you please give me an example of how can I Mock ForeignKey column behavior? –  May 09 '18 at 14:01
  • I have updated my question that shows I'm using the real context. –  May 09 '18 at 14:06
  • You are still not testing the real context, because you pass a fake one through the constructor of the service here: ` var service = new Class2Service(mockContext.Object);` Actually it looks like an impossible and useless thing to test the foreign key behavior because it is actually a DB's business. Please check out this answer to a pretty similar question: https://stackoverflow.com/questions/6904139/fake-dbcontext-of-entity-framework-4-1-to-test/6904479#6904479 – Andrew K May 09 '18 at 14:15
  • So what should I do? Can you please append the solution in your answer? I'm really confused since I'm really new to Mocking –  May 09 '18 at 14:16
  • Still, if you'd like to test the AddClass2 method you could set an IQueriable as a source for the DataContext or use the real database. – Andrew K May 09 '18 at 14:20
  • Thanks a lot. So can you please give me an example of how can I apply IQueriable with my example? If I set IQueriable with this fake context is foreignkey work? –  May 09 '18 at 14:21
  • I have provided a sample in the original answer. The foreign key will not work in this case. – Andrew K May 09 '18 at 14:24
  • Oh, It seems to be very complicated than I imagine! How can I use this complex example with my simple requirement? –  May 09 '18 at 14:24
  • You add MockDbSetExtensions class first.Then you create a new List which will be used as a source and call SetSource on your mock DbSet passing this list _before_ setting up the context. Will be smth like: ` var mockSet = new Mock>(); var list = new List(); mockSet .SetSource(list); mockContext.Setup(m => m.Class2s).Returns(mockSet.Object); ` – Andrew K May 09 '18 at 14:30