34

I am working on an ASP.Net Core MVC Web application.

My Solution contains 2 projects:

  • One for the application and
  • A second project, dedicated to unit tests (XUnit).

I have added a reference to the application project in the Tests project.

What I want to do now is to write a class in the XUnit Tests project which will communicate with the database through entity framework.

What I was doing in my application project was to access to my DbContext class through constructor dependency injection.

But I cannot do this in my tests project, because I have no Startup.cs file. In this file I can declare which services will be available.

So what can I do to get a reference to an instance of my DbContext in the test class?

Matt
  • 25,467
  • 18
  • 120
  • 187
Bob5421
  • 7,757
  • 14
  • 81
  • 175
  • Try this xunit di support built into xunit framework: nuget.org/packages/Xunit.Di, so that you can inject services dependencies the same way as you do for any other applications. – cli Oct 03 '21 at 12:00

4 Answers4

44

You can implement your own service provider to resolve DbContext.

public class DbFixture
{
    public DbFixture()
    {
        var serviceCollection = new ServiceCollection();
        serviceCollection
            .AddDbContext<SomeContext>(options => options.UseSqlServer("connection string"),
                ServiceLifetime.Transient);

        ServiceProvider = serviceCollection.BuildServiceProvider();
    }

    public ServiceProvider ServiceProvider { get; private set; }
}

public class UnitTest1 : IClassFixture<DbFixture>
{
    private ServiceProvider _serviceProvider;

    public UnitTest1(DbFixture fixture)
    {
        _serviceProvider = fixture.ServiceProvider;
    }

    [Fact]
    public void Test1()
    {
        using (var context = _serviceProvider.GetService<SomeContext>())
        {
        }
    }
} 

But bear in your mind using EF inside a unit test is not a good idea and it's better to mock DbContext.

The Anatomy of Good Unit Testing

Mohsen Esmailpour
  • 11,224
  • 3
  • 45
  • 66
  • What is DatabaseSeeder ? Should i put DbFixture in a Service subfolder ? – Bob5421 Jun 19 '18 at 06:44
  • Oops copy paste mistake. You can put `DbFixture` anywhere inside test project. I create `TestSetup` or `TestInfrastructure` folder and put such classes in these folders. – Mohsen Esmailpour Jun 19 '18 at 07:08
  • Instead of your line with AddEntityFrameworkSqlServer, i am using this: serviceCollection.AddDbContext(options => options.UseSqlServer("connection string") Is there a big difference ? – Bob5421 Jun 19 '18 at 07:15
  • No both works fine and `AddEntityFrameworkSqlServer` registers additional services. – Mohsen Esmailpour Jun 19 '18 at 07:20
  • One more thing: Are you sure i do not have to "declare" this DbFixture class somewhere ? I have a warning which says the class is never instancied and i have a runtime error: The following constructor parameters did not have matching fixture data: DbFixture fixture – Bob5421 Jun 19 '18 at 07:23
  • hi @MohsenEsmailpour can you answer this question here, I need to grab all services, and use them on call for integration next, not just db context, Thanks ! https://stackoverflow.com/questions/57331395/net-core-dependency-injection-in-xunit-test-following-constructor-parameters –  Aug 02 '19 at 20:45
  • https://stackoverflow.com/questions/57331395/net-core-execute-all-dependency-injection-in-xunit-test-for-appservice-reposit/57331714 this is another great question –  Aug 03 '19 at 00:08
  • @AlanWalker Sure I will check the question ASAP. – Mohsen Esmailpour Aug 03 '19 at 10:16
  • Tried to create a sample but didn't run (LinqPad 6: Query -> Add XUnit Test Support and then pasted your code; which is usually a good starting point for testing). How to reference ServiceCollection, ServiceLifetime, ServiceProvider? And could you give a short snippet for SomeContext? – Matt Nov 24 '20 at 10:46
  • The Anatomy of Good Unit Testing - unfortunately the link you provided is broken, could you look it up and fix it please? – Matt Nov 24 '20 at 10:47
  • **Important:** `.UseSqlServer` needs `Microsoft.EntityFrameworkCore.SqlServer`package to be loaded. This is due to [reported issue #7891 wit EF Core](https://github.com/dotnet/efcore/issues/7891). – Matt Nov 25 '20 at 14:14
  • Ok, to test this I used Adventureworks, added class `SomeContext` with `public SomeContext(DbContextOptions options) : base(options)` and `public DbSet AWBuildVersions { get; set; }`, implemented `AWBuildVersion` with all properties and added query `context.AWBuildVersions.Select(s=>s.DatabaseVersion).FirstOrDefault().Dump();` to the using statement in the code snippet. This query gives me the error The entity type 'AWBuildVersion' requires a primary key to be defined.` - but I used the [Key] attribute for `SystemInformationID`. Do you have any idea how to get it working? – Matt Nov 26 '20 at 09:44
  • After using [Key], [Table("TableName")] and [Column("ColumName"] annotations it was still not working. Then I found why: I had defined the properties in the table class "as is", i.e. without `{ get; set; }` - after adding that it was working fine! – Matt Nov 26 '20 at 09:59
  • 1
    @Matt I fixed the broken link. – Mohsen Esmailpour Nov 30 '20 at 11:26
  • @MohsenEsmailpour - Thank you, that is indeed a nice summary how to properly design (write) unit tests. – Matt Nov 30 '20 at 15:50
7

You can use Xunit.DependencyInjection

Mr. Pumpkin
  • 6,212
  • 6
  • 44
  • 60
  • 31
    Expanding on what this does and why it's better than other approaches would be super useful here. Also adding a small sample demonstrating how it looks when used would be great . – noelicus May 09 '19 at 15:32
  • https://stackoverflow.com/questions/63269460/how-to-use-xunit-dependency-constructor-injection-to-create-my-concrete-classes Working through the problem – Golden Lion Aug 05 '20 at 16:52
0

For unit tests you need to mock your context.

There is a great nuget package for mocking that is called Moq.

Some help to get you started:

public ClassName : IDisposable
{
    private SomeClassRepository _repository;
    private Mock<DbSet<SomeClass>> _mockSomeClass;

    public ClassName() 
    {
        _mockSomeClass = new Mock<DbSet<SomeClass>>();

        var mockContext = new Mock<IApplicationDbContext>();
        mockContext.SetupGet(c => c.SomeClass).Returns(_mockSomeClass.Object);

        _repository = new SomeClassRepository(mockContext.Object);
    }

    public void Dispose()
    {
        // Anything you need to dispose
    }

    [Fact]
    public void SomeClassTest()
    {
        var someClass = new SomeClass() { // Initilize object };

        _mockSomeClass.SetSource(new[] { someClass });

        var result = _repository.GetSomethingFromRepo( ... );

        // Assert the result
    }
}

For integration tests you do the same thing but the setup is:

_context = new ApplicationDbContext();

Make sure that your TestClass inherits from IDisposable (TestClass : IDisposable) so that you can dispose the context after each test.

https://xunit.github.io/docs/shared-context

Paul Adam
  • 251
  • 1
  • 2
  • 11
0

You can to use package Microsoft.EntityFrameworkCore.InMemory

var _dbContextOptions = new DbContextOptionsBuilder<DbContext>().UseInMemoryDatabase(Guid.NewGuid().ToString()).Options;

And then

var context = new DbContext(_dbContextOptions);