locked
Integration test and initialize the database by accessing service through factory.Server and overriding a service RRS feed

  • Question

  • User-585144208 posted

    To do the integration test in asp.net core, I have defined a CustomWebApplicationFactory as below : 

      public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
        {
            protected override void ConfigureWebHost(IWebHostBuilder builder)
            {
                builder.ConfigureServices(services =>
                {
                    // Remove the app's ApplicationDbContext registration.
                    var descriptor = services.SingleOrDefault(
                        d => d.ServiceType ==
                            typeof(DbContextOptions<MydbContext>));
    
                    if (descriptor != null)
                    {
                        services.Remove(descriptor);
                    }
    
                    // Add ApplicationDbContext using an in-memory database for testing.
                    services.AddDbContext<PodcastDataContext>(options =>
                    {
                        options.UseInMemoryDatabase("InMemoryDbForTesting");
                    });
    
                    
    
                });
            }

    I also have defined a base class for all integration tests as below : 

     public class BaseIntegrationTest : IClassFixture<CustomWebApplicationFactory<Startup>>
        {
            protected readonly HttpClient _client;
            protected CustomWebApplicationFactory<Startup> _factory;
            private static bool alreadyCalledDb = false;
    
    
            public BaseIntegrationTest(CustomWebApplicationFactory<Startup> factory)
            {
                _factory = factory;
    
                _client = factory.WithWebHostBuilder(r=> {
                    r.ConfigureTestServices(s =>
                    {
                        s.AddTransient(typeof(ISMSSender), typeof(FakeSmsService));
                    });
                }).CreateClient(new WebApplicationFactoryClientOptions
                {
                    AllowAutoRedirect = false
                });
    
               initDataBase();
            }
    
            private void initDataBase()
            {
                if (!alreadyCalledDb)
                {
                    using (var scope = this._factory.Server.Host.Services.CreateScope())
                    {
                        // access db context and add data to inmemory database
    
    
                        alreadyCalledDb = true;
    
                    }
                }
    
            }
    }

    The problem is that when initDataBase is called, the _factory.Server is null.

    If I remove the following lines, it works :

    WithWebHostBuilder(r=> {
                    r.ConfigureTestServices(s =>
                    {
                        s.AddTransient(typeof(ISMSSender), typeof(FakeSmsService));
                    });
                }).

    But this way I can not inject my mock service. 

    Monday, December 16, 2019 8:48 PM

All replies

  • User711641945 posted

    Hi b.dev,

    You need to inject the mock service in your test class like below:

    public class BaseIntegrationTest : IClassFixture<CustomWebApplicationFactory<Startup>>
    {
          protected CustomWebApplicationFactory<Startup> _factory;
          private static bool alreadyCalledDb = false;
          public BaseIntegrationTest(CustomWebApplicationFactory<Startup> factory)
          {
              _factory = factory;
              //initDataBase();
          }
          [Fact]
          private void initDataBase()
          {
              void ConfigureTestServices(IServiceCollection services) =>
                    services.AddSingleton<ISMSSender>(new TestFakeSmsService());
              var client = _factory
                    .WithWebHostBuilder(builder =>
                        builder.ConfigureTestServices(ConfigureTestServices))
                    .CreateClient();
              if (!alreadyCalledDb)
              {
    
                  using (var scope = this._factory.Server.Host.Services.CreateScope())
                  {
                      // access db context and add data to inmemory database
                      alreadyCalledDb = true;
                  }
              }
    
          }
          public class TestFakeSmsService : ISMSSender
          {
              //...
          }
    }

    I suggest that you could run the official sample to learn more about integration test:

    https://github.com/aspnet/AspNetCore.Docs/blob/master/aspnetcore/test/integration-tests/samples/2.x/IntegrationTestsSample/tests/RazorPagesProject.Tests/IntegrationTests/AuthTests.cs

    Best Regards,

    Rena

    Tuesday, December 17, 2019 6:09 AM
  • User1120430333 posted

    But this way I can not inject my mock service.

    There is a no mocked anything in an integration test. An integration test is against live objects  The objects are real objects not mocked objects. The factory object has to be instanced for real and DI injected into the class that needs the object. You can use an in-memory database or a live integration test database out on database server for an intergradation test.

    You can even use a unit testing framework for the integration test, like Nunit, AutoFact, etc. and etc. The setup of the unit testing framework should instance the factory object for real  so that it can be DI. Again, an integration test is about testing everything for real to test real functionality

    Tuesday, December 17, 2019 4:36 PM
  • User-585144208 posted

    I used wrong word. I meant Fake service. As you see in the code, I want to use FakeSmsService for  ISmsSender dependency  

    Wednesday, December 18, 2019 8:39 PM
  • User1120430333 posted

    I used wrong word. I meant Fake service. As you see in the code, I want to use FakeSmsService for  ISmsSender dependency  

    If you are faking something, then it's not real. Again, integration testing is about doing everything for real  doing everything for real in a testing environment. Whatever you're trying to fake, you need to unfake it, use the real component, and test it as if all components of the test were running in a production environment for real. 

    https://www.softwaretestinghelp.com/what-is-integration-testing/

    <copied>

    Integration testing is done to test the modules/components when integrated to verify that they work as expected i.e. to test the modules which are working fine individually does not have issues when integrated.

    <end>

    http://softwaretestingfundamentals.com/integration-testing/

    <copied>

    Analogy

    During the process of manufacturing a ballpoint pen, the cap, the body, the tail and clip, the ink cartridge and the ballpoint are produced separately and unit tested separately. When two or more units are ready, they are assembled and Integration Testing is performed. For example, whether the cap fits into the body or not.

    <end>

    Wednesday, December 18, 2019 10:30 PM
  • Thursday, December 19, 2019 10:42 PM
  • User1120430333 posted

    First, you were talking mock that was switch to fake, which concerns unit testing an not integration testing.

    Secondly, I would be testing components for real  on whatever the components are supposed to be doing while running in  a QA or production situation but using a test harness and UT framework doing an arrange, act and assert. Just becuase you passed some integration test that is not using what would normally be  in a QA or production environment doesn't mean things are going to work as planned when code is pushed to the QA or production environment. 

    Friday, December 20, 2019 4:41 AM