Существует множество статей о модульном тестировании для 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