База данных в памяти иногда кажется пустой в интеграционных тестах - PullRequest
0 голосов
/ 01 апреля 2020

Я пытаюсь использовать базу данных в памяти в asp. net core web api project для тестирования интеграции.

Для аутентификации я использую BasicAuthenticationHandler, который проверяет имя пользователя / пароль в отношении пользователей хранится в базе данных (в БД в памяти для тестирования интеграции).

Код отлично работает для запроса Get , затем для запроса Post , но затем для следующего Get запросов выглядит как Unathorized , потому что BasicAuthenticationHandler был вызван с пустым контекстом базы данных. Таким образом, проблема возникает только после Post запроса.

Вот код:

Startup.cs:

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<UsersDbContext>(opt => opt.UseInMemoryDatabase("UsersDb"));

            services.AddControllers();

            // configure basic authentication 
            services.AddAuthentication("BasicAuthentication")
                .AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("BasicAuthentication", null);
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseHttpsRedirection();
            app.UseRouting();

            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }

BasicAuthenticationHandler.cs

   public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
    {
        private readonly UsersDbContext _context;

        public BasicAuthenticationHandler(
            IOptionsMonitor<AuthenticationSchemeOptions> options,
            ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock,
            UsersDbContext context)
            : base(options, logger, encoder, clock)
        {
            _context = context;
        }

        public async Task<User> Authenticate(string username, string password)
        {
            // !!! SOMETIMES _context is empty here!!!!!
            var user = await Task.Run(() => _context.Users.SingleOrDefault(x => x.Username == username && x.Password == password));

            return user;
        }

        protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
        {
            // ... parsing
            user = await Authenticate(username, password);
            // ... preparing
            return AuthenticateResult.Success(ticket);
        }
    }

UsersController.cs

public class UsersController : ControllerBase
    {
        private readonly UserDbContext _context;

        public UsersController(UserDbContext context)
        {
            _context = context;
        }

        [HttpGet]
        public async Task<ActionResult<IEnumerable<User>>> GetUsers()
        {
            return await _context.Users.ToListAsync();
        }

        [HttpPost]
        public async Task<ActionResult<User>> PostUser(User user)
        {
            _context.Users.Add(user);
            await _context.SaveChangesAsync();

            return CreatedAtAction("GetUser", new { id = user.UserId }, user);
        }
    }

Интеграционный тест использует CustomWebApplicationFactory и выглядит следующим образом:

CustomWebApplicationFactory.cs:

    public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<Startup>
    {
        protected override void ConfigureWebHost(IWebHostBuilder builder)
        {
            builder.ConfigureServices(services =>
            {
                var serviceProvider = new ServiceCollection()
                    .AddEntityFrameworkInMemoryDatabase()
                    .BuildServiceProvider();

                services.AddDbContext<UserDbContext>(options =>
                {
                    options.UseInMemoryDatabase("UsersDb");
                    options.UseInternalServiceProvider(serviceProvider);
                });

                var sp = services.BuildServiceProvider();

                using (var scope = sp.CreateScope())
                {
                    var scopedServices = scope.ServiceProvider;
                    var appDb = scopedServices.GetRequiredService<UserDbContext>();

                    var logger = scopedServices.GetRequiredService<ILogger<CustomWebApplicationFactory<TStartup>>>();

                    appDb.Database.EnsureCreated();

                    // Seed the database with some specific test data.
                    if (!appDb.Users.Any())
                    {
                        appDb.Users.Add(new User() { Username = "admin", Password = "admin" });
                        appDb.SaveChanges();
                    }
                }
            });
        }
    }

UsersControllerTests.cs

    public class UsersControllerTests
    {
        CustomWebApplicationFactory<Startup> factory = new CustomWebApplicationFactory<Startup>();
        private HttpClient _client;

        [SetUp]
        public void Setup()
        {
            _client = factory.CreateClient();

            _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
                AuthenticationSchemes.Basic.ToString(),
                Convert.ToBase64String(Encoding.ASCII.GetBytes($"admin:admin"))
            );
        }

        [Test]
        public async Task Test1Async()
        {
            var httpResponse = await _client.GetAsync("/api/Users");
            httpResponse.EnsureSuccessStatusCode(); // works

            // add a user
            var json = JsonConvert.SerializeObject(new User()
            {
                Username = "user1",
                Password = "pass1"
            });
            var httpContent = new StringContent(json, Encoding.UTF8, "application/json");
            httpResponse = await _client.PostAsync("/api/Users/", httpContent);
            httpResponse.EnsureSuccessStatusCode(); // works

            // check users
            httpResponse = await _client.GetAsync("/api/Users");
            httpResponse.EnsureSuccessStatusCode(); // !!!Unathorized!!!!
...
...