Как протестировать xUnit против реальных таблиц идентификаторов ASP.NET Core 1.0 AccountController без насмешек?

Существует множество статей о модульном тестировании для ASP.NET Core 1.0 AccountController.

Кажется, что ни один из них на самом деле не включает проверку содержимого реальных таблиц идентификации Asp.Net Core 1.0.

Есть 7 столов:

AspNetUsers
AspNetUserTokens
AspNetUserLogins
AspNetUserRoles
AspNetUserClaims
AspNetRoles
AspNetRoleClaims

в приложении .NET Core Web API поток (за исключением невидимых частей внешней среды кода) сначала проходит через Main

    public static void Main(string[] args)
    {
        var host = new WebHostBuilder()
            .UseKestrel()
            .UseContentRoot(Directory.GetCurrentDirectory())
            .UseIISIntegration()
            .UseStartup<Startup>()
            .Build();

        host.Run();
    }

Из var host = ... в Startup.cs вводим public Startup(IHostingEnvironment env); после этого метод времени выполнения public void ConfigureServices(IServiceCollection services) разрешает настройку:

        // Add framework services.
        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

        services.AddIdentity<ApplicationUser, IdentityRole>()
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddDefaultTokenProviders();

        services.AddMvc();

и так далее

Наконец, host.Run(); переводит наше приложение .NET Core Web API в режим прослушивания.

http://localhost:58796/account/login приводит к возврату формы Войти.

Конструктор AccountController выглядит следующим образом:

    public AccountController(
        UserManager<ApplicationUser> userManager,
        SignInManager<ApplicationUser> signInManager,
        IEmailSender emailSender,
        ISmsSender smsSender,
        ILoggerFactory loggerFactory)
    {
        _userManager = userManager;
        _signInManager = signInManager;
        _emailSender = emailSender;
        _smsSender = smsSender;
        _logger = loggerFactory.CreateLogger<AccountController>();
    }

Это означает, что UserManager и SignInManager должны быть переданы в AccountController; SignInManager не может быть нулевым и требует ненулевого UserManager и ненулевого регистратора:

   public SignInManager(UserManager<TUser> userManager,
       IHttpContextAccessor contextAccessor,
       IUserClaimsPrincipalFactory<TUser> claimsFactory,
       IOptions<IdentityOptions> optionsAccessor, 
       ILogger<SignInManager<TUser>> logger);

Настройка логгера в нашем методе xUnit.net относительно проста:

        ILoggerFactory loggerFactory = new LoggerFactory()
                                      .AddConsole()
                                      .AddDebug();

Настройка userManager в нашем методе xUnit.net более сложна:

    public UserManager(IUserStore<TUser> store,
            IOptions<IdentityOptions> optionsAccessor,
            IPasswordHasher<TUser> passwordHasher,
            IEnumerable<IUserValidator<TUser>> userValidators, 
            IEnumerable<IPasswordValidator<TUser>> passwordValidators, 
            ILookupNormalizer keyNormalizer,
            IdentityErrorDescriber errors,
            IServiceProvider services,
            ILogger<UserManager<TUser>> logger);

Частью проблемы является сложность реализации interface IUserStore<TUser> контракта для магазина:

// Summary:  Provides an abstraction for a store which manages user accounts.
//   TUser:  The type encapsulating a user.
public interface IUserStore<TUser> : IDisposable where TUser : class
{
    Task<IdentityResult> CreateAsync(TUser user, CancellationToken cancellationToken);
    Task<IdentityResult> DeleteAsync(TUser user, CancellationToken cancellationToken);
    Task<TUser> FindByIdAsync(string userId, CancellationToken cancellationToken);
    Task<TUser> FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken);
    Task<string> GetNormalizedUserNameAsync(TUser user, CancellationToken cancellationToken);
    Task<string> GetUserIdAsync(TUser user, CancellationToken cancellationToken);
    Task<string> GetUserNameAsync(TUser user, CancellationToken cancellationToken);
    Task SetNormalizedUserNameAsync(TUser user, string normalizedName, CancellationToken cancellationToken);
    Task SetUserNameAsync(TUser user, string userName, CancellationToken cancellationToken);
    Task<IdentityResult> UpdateAsync(TUser user, CancellationToken cancellationToken);
}

При обычном использовании AccountController большая часть сложности скрывается за кулисами благодаря подключению веб-API ASP.NET Core 1.0, которое обеспечивает доступ к таблицам базы данных Identity через EF Core 1.0.

Для модульного тестирования с xUnit.net без насмешек (поскольку мы хотим получить доступ к «настоящей базе данных») проблема заключается в следующем:

 [Fact]
 // Arrange
     ...
 // Act
     ... // the AccountController is not available yet
 var accountController
     = new AccountController(userManager: ...,
              signInManager: ...,
                emailSender: null,
                smsSender: null,
                loggerFactory: loggerFactory); // can NOT be null
.............

в двух словах, проблема заключается в улочке 22 ~~ Start.cs устанавливает все, что нам нужно, однако наш тест xUnit.net выполняется до запуска Start.cs.

.zip можно найти здесь github.com/gerryLowry/RawCoreAPIxUnit

неархивированную версию можно найти здесь github.com/gerryLowry/EF_Core_testing_experiments


person gerryLowry    schedule 04.09.2016    source источник
comment
незавершенная работа Тест xUnit.net [Факт] GerryLowryAtSomewhereComCanLogIn() только кажется пройденным, потому что это неполный тест.   -  person gerryLowry    schedule 04.09.2016
comment
почему вы хотите проверить это в модульном тесте? Вы не тестируете свой собственный код. Похоже, вы хотите создать интеграционный тест, используя свое приложение/код. Так что подберите подходящий для него фреймворк.   -  person Jocke    schedule 10.02.2018


Ответы (1)


Может быть, попробовать Microsoft.AspNetCore.TestHost (поставляется с Asp.Net Core для интеграционного тестирования).

Вы можете инициализировать TestServer в конструкторе вашего тестового класса и подключить к нему свой собственный Startup.cs. Затем зарегистрируйте фиктивные реализации всех зависимостей AccountController, кроме одной реальной зависимости от базы данных.

Для конструкции «настоящая база данных» UserManager с UserStore и реализацией Entity Framework IUserStore (ссылка)

person yohanmishkin    schedule 10.02.2018