3

I write a unit test using xunit and Moq in asp.net core, i follow this Article for this, but i got this error:

Invalid setup on a non-virtual (overridable in VB) member: m => m.Blogs

this is what i tried:

 [Fact]
    public void CreateBlog()
    {
        var mockDbSet = new Mock<DbSet<Blog>>();
        var mockContext = new Mock<Context>();

        mockContext.Setup(m => m.Blogs).Returns(mockDbSet.Object); //In this line i got error

        var service = new BlogController(mockContext.Object);
        service.AddBlog("ADO.NET Blog", "adtn.com");

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

these are my models class:

  public class Blog
  {
    public int BlogId { get; set; }
    public string Name { get; set; }
    public string Url { get; set; }
    public virtual IList<Post> Posts { get; set; }
  }

and:

 public class Post
 {
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public DateTime RegisterDate { get; set; }
    public int BlogId { get; set; }
    public virtual Blog Blog { get; set; }
 }

and this is my controller:

  public class BlogController : Controller
  {
    private readonly Context _context;
    public BlogController(Context ctx)
    {
        _context = ctx;
    }

    [HttpPost]
    public Blog AddBlog(string name, string url)
    {
        var blog = new Blog() { Name = name, Url = url };
        _context.Blogs.Add(blog);
        _context.SaveChanges();
        return blog;
    }
}

UPDATE: I make dbset in the context virtual, but it gives another error:

Can not instantiate proxy of class: EntitFrameworkCore.Models.Context

what is the problem?

Community
  • 1
  • 1
pmn
  • 2,176
  • 6
  • 29
  • 56
  • 1
    Error message says it ;) – Tseng Dec 28 '16 at 14:23
  • I do what the article said, so the article goes wrong? – pmn Dec 28 '16 at 14:25
  • Blogs property in context needs to be virtual – Nkosi Dec 28 '16 at 14:25
  • you could also abstract the context. because the mock is actually creating an instance of the context which is still trying to connect to the database – Nkosi Dec 28 '16 at 14:30
  • Mock can't instantiate it, because the DbContext has no parameterless constructor. Look my answer below and consider using InMemoryDb or use DbContextOptionsBuilder to build it yourself, but then you can't mock it and have to use real DB which is no go in unit tests – Tseng Dec 28 '16 at 14:31

2 Answers2

2

Make sure Blogs property in Context class is overridable

public class Context : DbContext 
{ 
    public virtual DbSet<Blog> Blogs { get; set; } 
} 

You should also consider abstracting the db context.

public interface IContext {
    DbSet<Blog> Blogs { get; set; } 
}

public class Context : DbContext, IContext { 
    public virtual DbSet<Blog> Blogs { get; set; } 
} 

Now it does not even matter if Context properties are virtual as you will be mocking the inherited interface when unit testing.

Your classes should also only depend on abstractions and not implementation details.

public class BlogController : Controller {
    private readonly IContext context;
    public BlogController(IContext context) {
        this.context = context;
    }
    //...other code
}

so that it can be mocked safely during isolated unit test.

[Fact]
public void CreateBlog() {
    //Arrange
    var mockDbSet = new Mock<DbSet<Blog>>();
    var mockContext = new Mock<IContext>();

    mockContext.Setup(m => m.Blogs).Returns(mockDbSet.Object);

    var service = new BlogController(mockContext.Object);

    //Act
    service.AddBlog("ADO.NET Blog", "adtn.com");

    //Assert
    mockDbSet.Verify(m => m.Add(It.IsAny<Blog>()), Times.Once());
    mockContext.Verify(m => m.SaveChanges(), Times.Once());
}
Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • it gives me another error : `Can not instantiate proxy of class: EntitFrameworkCore.Models.Context` do u have any idea about it please? – pmn Dec 28 '16 at 14:32
  • Never tried splitting the context in an interface (I prefer other forms of abstractions), but will the ASP.NET Core IoC be able to inject it w/o an explicit `services.AddScpüed(provider => provider.GetService());` call in Startup.cs ? – Tseng Dec 28 '16 at 14:49
  • @Tseng u prefer other forms of abstractions! what is ur mean? – pmn Dec 28 '16 at 14:52
  • 1
    @pejman: Repository, CQRS. But both are out of the scope of the question – Tseng Dec 28 '16 at 14:58
  • @Tseng you would need to configure it with IoC container in startup. `services.AddScoped(p=> new Context(options))` – Nkosi Dec 28 '16 at 14:59
  • @Nkosi why we should create a Icontext? we can do test by mocking the context? – pmn Jan 18 '17 at 07:45
1

The error message is already very clear about it.

mockContext.Setup(m => m.Blogs)
    .Returns(mockDbSet.Object);

I assume Blogs is your DbSet<Blog> property which is not virtual. Mocks only work on method/properties marked virtual or on interfaces.

You shouldn't really have to Mock your DbContext. Instead rather use a DbContext backed by an in memory database. See docs on how to setup your context to use the inmemory provider, that's the main purpose of the InMemory provider.

var options = new DbContextOptionsBuilder<BloggingContext>()
    .UseInMemoryDatabase(databaseName: "sompe_database_name")
    .Options;

var context = new AppDbContext(options);
Tseng
  • 61,549
  • 15
  • 193
  • 205
  • 1
    It's best to use in memory DB (and effectively turning it into integration test). Your code is not very well separated/abstracted and tightly coupled to the persistence layer and your db concerns leak into other parts of your applications. That's the reason why you have troubles with it – Tseng Dec 28 '16 at 14:38
  • I read some article that use an interface for their context, it's good that i do like them? – pmn Dec 28 '16 at 14:42
  • 1
    It may be a valid option (see Nikosi's example), though you still may run into issues that your `IQueryable` and `IDbSet` leaking into your layers as this allows queries to be modified in any part of application (which may cause some unexpected joins and unexpectedly low performance). For learning or small applications it's fine, but may be an issue in enterprise level applications – Tseng Dec 28 '16 at 14:46