1

I'm trying to implement integration test so I can test my endpoints using HttpClient only

public async Task TestEndPoint()
{          
    using (var response = await _client.GetAsync($"/api/car/{id}"))
    {
        //...         
    }
}

I'm using Fixture class to help me with this

private readonly TestServer _server;
public Fixture()
{
    var dbContextOptions = new DbContextOptionsBuilder<CarDbContext>()
                    .UseInMemoryDatabase(Guid.NewGuid().ToString())
                    .Options;

    var mockContext = new Mock<CarDbContext>(dbContextOptions);
    var mockOwnerSet = new Mock<DbSet<Owner>>();
    var mockCarSet = new Mock<DbSet<Car>>();
    mockContext.Setup(m => m.Owners).Returns(mockOwnerSet.Object);
    mockContext.Setup(m => m.Cars).Returns(mockCarSet.Object);          

    var carService = new CarService(mockContext.Object);

    _server = new TestServer(new WebHostBuilder()
        .ConfigureAppConfiguration((context, conf) =>
        {
            conf.AddJsonFile(@Directory.GetCurrentDirectory() + "../appsettings.json");
        }).UseStartup<Startup>()
            .ConfigureServices(services =>
            {
                services.AddDbContext<CarDbContext>(options => options.UseInMemoryDatabase("Test"));
                services.AddScoped<ICarService>(_ => carService);
            })
        );

    Client = _server.CreateClient();    
}   

Inside Startup.cs

public class Startup
{
   public void ConfigureServices(IServiceCollection services)
   {
       services.AddDbContext<CarDbContext>(options => { options.UseSqlServer(Configuration.GetConnectionString("Db")); });
       services.AddTransient<ICarService, CarService>();
   }
}

Service class

public class CarService: ICarService {
    private readonly CarDbContext _carDbContext;

    public CarService(CarDbContext carDbContext)
    {
        _carDbContext = carDbContext;
    }

    public async Task<Car> GetAsync(int id)
    {
        return await _carDbContext.Cars.FindAsync(id); //this always returns null
    }
}

My question is: Why mock db context is not being used inside CarService

return await _carDbContext.Cars.FindAsync(id); always returns null

Update:

private readonly TestServer _server;
public TestServer Server;
public Fixture()
{
    _server = new TestServer(new WebHostBuilder()
        .ConfigureAppConfiguration((context, conf) =>
        {
            conf.AddJsonFile(@Directory.GetCurrentDirectory() + "../appsettings.json");
        }).UseStartup<Startup>()
            .ConfigureServices(services =>
            {
                services.AddDbContext<CarDbContext>(options => options.UseInMemoryDatabase(Guid.NewGuid().ToString()));
            })
        );

    Client = _server.CreateClient();    
    Server = _server;
}

// inside test method

using (var scope = _server.Host.Services.CreateScope())
{
    var db = scope.ServiceProvider.GetRequiredService<CarDbContext>();
    db.Database.EnsureDeleted();
    Utilities.Seed(db);
}

And Seed data

public static class Utilities
{
    public static void Seed(CarDbContext db)
    {
         db.Cars.Add(new Car("1", "White", "Toyota"));        
         db.SaveChanges(true);
        }
  }

What exactly am I doing wrong where? I'm still getting null when retrieving data in

public async Task<Car> GetAsync(int id)
{
    return await _carDbContext.Cars.FindAsync(id); //this always returns null
}
user1765862
  • 13,635
  • 28
  • 115
  • 220
  • Have you put a breakpoint at the line where `FindAsync` returns `null` and seen that the `CarDbContext` really is the mocked type you are expecting? If it is not then perhaps the second call to `AddDbContext` is being ignored (i.e. is behaving like `TryAdd` as opposed to `Add`) – thisextendsthat Oct 10 '19 at 15:20
  • You're right, CarDbContext in the service is not mocked type. What exactly are you suggesting? – user1765862 Oct 10 '19 at 15:31

1 Answers1

3

You don't need to mock the context or your service. Remove everything before the _server = new TestServer line. In your web host setup, you've already set the context to use the in-memory provider, so that's all that's necessary. The context will be injected into the service and the service will be injected where it's needed. The only thing that's changing here is the context connection. Instead of hitting something real like a SQL Server instance, it will simply be storing and retrieving from memory instead.

The only other thing you need to do for your test is to seed the "database" with the data you want to test against. In your test, you'll need to access the test server instance and do:

using (var scope = _server.Host.Services.CreateScope())
{
    var context = scope.ServiceProvider.GetRequiredService<CarDbContext>();
    context.EnsureDeleted();
    // seed test data
}

// test code
Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • 1
    [This answer](https://stackoverflow.com/a/50434512/2557855) can also be helpful. `ConfigureServices` on `TestServer` will run **before** original `ConfigureServices`, hence it will be overriden again by original `DbContext`. `ConfigureTestServices` will run **after** original `ConfigureServices`. At least this was the behavior a year ago, when I did it last time. – Artur Oct 10 '19 at 16:39
  • @user1765862 did you check my comment above? – Artur Oct 11 '19 at 08:16