使用.net核心和身份框架进行集成测试

时间:2018-03-14 09:20:04

标签: c# asp.net-core integration-testing asp.net-core-identity

我无法在任何地方找到任何答案。 我已经阅读了多篇文章并查看了大量的源代码,但似乎没有任何帮助。

http://www.dotnetcurry.com/aspnet-core/1420/integration-testing-aspnet-core

https://www.davepaquette.com/archive/2016/11/27/integration-testing-with-entity-framework-core-and-sql-server.aspx

https://docs.microsoft.com/en-us/aspnet/core/testing/integration-testing

我遇到的问题是解析服务而不是使用 HttpClient 来测试控制器。 这是我的初创班:

public class TestStartup: Startup, IDisposable
{

    private const string DatabaseName = "vmpyr";

    public TestStartup(IConfiguration configuration) : base(configuration)
    {
    }

    protected override void EnsureDatabaseCreated(DatabaseContext dbContext)
    {
        DestroyDatabase();
        CreateDatabase();
    }

    protected override void SetUpDataBase(IServiceCollection services)
    {
        var connectionString = Database.ToString();
        var connection = new SqlConnection(connectionString);
        services
            .AddEntityFrameworkSqlServer()
            .AddDbContext<DatabaseContext>(
                options => options.UseSqlServer(connection)
            );
    }

    public void Dispose()
    {
        DestroyDatabase();
    }

    private static void CreateDatabase()
    {
        ExecuteSqlCommand(Master, $@"Create Database [{ DatabaseName }] ON (NAME = '{ DatabaseName }', FILENAME = '{Filename}')");
        var connectionString = Database.ToString();
        var optionsBuilder = new DbContextOptionsBuilder<DatabaseContext>();
        optionsBuilder.UseSqlServer(connectionString);
        using (var context = new DatabaseContext(optionsBuilder.Options))
        {
            context.Database.Migrate();
            DbInitializer.Initialize(context);
        }
    }

    private static void DestroyDatabase()
    {
        var fileNames = ExecuteSqlQuery(Master, $@"SELECT [physical_name] FROM [sys].[master_files] WHERE [database_id] = DB_ID('{ DatabaseName }')", row => (string)row["physical_name"]);
        if (!fileNames.Any()) return;
        ExecuteSqlCommand(Master, $@"ALTER DATABASE [{ DatabaseName }] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; EXEC sp_detach_db '{ DatabaseName }'");
        fileNames.ForEach(File.Delete);
    }

    private static void ExecuteSqlCommand(SqlConnectionStringBuilder connectionStringBuilder, string commandText)
    {
        using (var connection = new SqlConnection(connectionStringBuilder.ConnectionString))
        {
            connection.Open();
            using (var command = connection.CreateCommand())
            {
                command.CommandText = commandText;
                command.ExecuteNonQuery();
            }
        }
    }

    private static List<T> ExecuteSqlQuery<T>(SqlConnectionStringBuilder connectionStringBuilder, string queryText, Func<SqlDataReader, T> read)
    {
        var result = new List<T>();
        using (var connection = new SqlConnection(connectionStringBuilder.ConnectionString))
        {
            connection.Open();
            using (var command = connection.CreateCommand())
            {
                command.CommandText = queryText;
                using (var reader = command.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        result.Add(read(reader));
                    }
                }
            }
        }
        return result;
    }

    private static SqlConnectionStringBuilder Master => new SqlConnectionStringBuilder
    {
        DataSource = @"(LocalDB)\MSSQLLocalDB",
        InitialCatalog = "master",
        IntegratedSecurity = true
    };

    private static SqlConnectionStringBuilder Database => new SqlConnectionStringBuilder
    {
        DataSource = @"(LocalDB)\MSSQLLocalDB",
        InitialCatalog = DatabaseName,
        IntegratedSecurity = true
    };

    private static string Filename => Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), $"{ DatabaseName }.mdf");
}

然后在我的集成测试中,我创建了2个安装类。第一个是 TestStartup

public class TestFixture<TStartup> : IDisposable where TStartup : class
{
    private readonly IServiceScope _scope;
    private readonly TestServer _testServer;

    public TestFixture()
    {
        var webHostBuilder = new WebHostBuilder().UseStartup<TStartup>();  

        _testServer = new TestServer(webHostBuilder);
        _scope = _testServer.Host.Services.CreateScope();
    }

    public TEntity Resolve<TEntity>() => _scope.ServiceProvider.GetRequiredService<TEntity>();

    public void Dispose()
    {
        _scope.Dispose();
        _testServer.Dispose();
    }
}

它处理我的所有数据库创建和服务配置。 第二个是我的 TestFixture 类:

Resolve

这(如您所见)创建了测试服务器,但也公开了应该解析我的服务的public class UserContext { private readonly UserManager<User> _userManager; private UserContext(TestFixture<TestStartup> fixture) => _userManager = fixture.Resolve<UserManager<User>>(); public static UserContext GivenServices() => new UserContext(new TestFixture<TestStartup>()); public async Task<User> WhenCreateUserAsync(string email) { var user = new User { UserName = email, Email = email }; var result = await _userManager.CreateAsync(user); if (!result.Succeeded) throw new Exception(result.Errors.Join(", ")); return user; } public async Task<User> WhenGetUserAsync(string username) => await _userManager.FindByNameAsync(username); } 方法。 现在来了我的考试。 我创建了一个 UserContext 类,如下所示:

[TestFixture]
public class UserManagerTests
{

    [Test]
    public async Task ShouldCreateUser()
    {
        var services = UserContext.GivenServices();
        await services.WhenCreateUserAsync("tim@tim.com");
        var user = await services.WhenGetUserAsync("tim@tim.com");
        user.Should().NotBe(null);
    }
}

然后我创建了一个测试:

1[vmpyr.Data.Models.User]' while attempting to activate 'Microsoft.AspNetCore.Identity.UserManager

不幸的是,当我运行测试时它会出错并声明:

  

消息:System.InvalidOperationException:无法解析类型&#39; Microsoft.AspNetCore.Identity.IUserStore services.AddIdentityCore<User>(null) 1 [vmpyr.Data.Models.User]&#39;的服务。

我认为这告诉我,虽然它找到了我的 UserManager 服务,但它找不到构造函数中使用的 UserStore 依赖项。 我看过public static IdentityBuilder AddIdentityCore<TUser>(this IServiceCollection services, Action<IdentityOptions> setupAction) where TUser : class { services.AddOptions().AddLogging(); services.TryAddScoped<IUserValidator<TUser>, UserValidator<TUser>>(); services.TryAddScoped<IPasswordValidator<TUser>, PasswordValidator<TUser>>(); services.TryAddScoped<IPasswordHasher<TUser>, PasswordHasher<TUser>>(); services.TryAddScoped<ILookupNormalizer, UpperInvariantLookupNormalizer>(); services.TryAddScoped<IdentityErrorDescriber>(); services.TryAddScoped<IUserClaimsPrincipalFactory<TUser>, UserClaimsPrincipalFactory<TUser>>(); services.TryAddScoped<UserManager<TUser>, UserManager<TUser>>(); if (setupAction != null) services.Configure<IdentityOptions>(setupAction); return new IdentityBuilder(typeof (TUser), services); } 并且看到它没有显示 UserStore 的注册:

.AddIdentity<User, IdentityRole>()

然后我查看了public static IdentityBuilder AddIdentity<TUser, TRole>(this IServiceCollection services, Action<IdentityOptions> setupAction) where TUser : class where TRole : class { services.AddAuthentication((Action<AuthenticationOptions>) (options => { options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme; options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme; options.DefaultSignInScheme = IdentityConstants.ExternalScheme; })).AddCookie(IdentityConstants.ApplicationScheme, (Action<CookieAuthenticationOptions>) (o => { o.LoginPath = new PathString("/Account/Login"); o.Events = new CookieAuthenticationEvents() { OnValidatePrincipal = new Func<CookieValidatePrincipalContext, Task>(SecurityStampValidator.ValidatePrincipalAsync) }; })).AddCookie(IdentityConstants.ExternalScheme, (Action<CookieAuthenticationOptions>) (o => { o.Cookie.Name = IdentityConstants.ExternalScheme; o.ExpireTimeSpan = TimeSpan.FromMinutes(5.0); })).AddCookie(IdentityConstants.TwoFactorRememberMeScheme, (Action<CookieAuthenticationOptions>) (o => o.Cookie.Name = IdentityConstants.TwoFactorRememberMeScheme)).AddCookie(IdentityConstants.TwoFactorUserIdScheme, (Action<CookieAuthenticationOptions>) (o => { o.Cookie.Name = IdentityConstants.TwoFactorUserIdScheme; o.ExpireTimeSpan = TimeSpan.FromMinutes(5.0); })); services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>(); services.TryAddScoped<IUserValidator<TUser>, UserValidator<TUser>>(); services.TryAddScoped<IPasswordValidator<TUser>, PasswordValidator<TUser>>(); services.TryAddScoped<IPasswordHasher<TUser>, PasswordHasher<TUser>>(); services.TryAddScoped<ILookupNormalizer, UpperInvariantLookupNormalizer>(); services.TryAddScoped<IRoleValidator<TRole>, RoleValidator<TRole>>(); services.TryAddScoped<IdentityErrorDescriber>(); services.TryAddScoped<ISecurityStampValidator, SecurityStampValidator<TUser>>(); services.TryAddScoped<IUserClaimsPrincipalFactory<TUser>, UserClaimsPrincipalFactory<TUser, TRole>>(); services.TryAddScoped<UserManager<TUser>, AspNetUserManager<TUser>>(); services.TryAddScoped<SignInManager<TUser>, SignInManager<TUser>>(); services.TryAddScoped<RoleManager<TRole>, AspNetRoleManager<TRole>>(); if (setupAction != null) services.Configure<IdentityOptions>(setupAction); return new IdentityBuilder(typeof (TUser), typeof (TRole), services); } 方法,而且似乎也没有注册 UserStore

+ (NSString *)contentTypeForImageData:(NSData *)data;

有谁知道如何解决我的 UserManager ? 任何帮助将不胜感激。

1 个答案:

答案 0 :(得分:3)

您在此处所做的就是测试您为测试代码而编写的代码。而且,即便如此,您最终希望测试的代码是框架代码,您不应该首先进行测试。身份由广泛的测试套件涵盖。您可以放心地假设像FindByNameAsync这样的方法有效。这极大地浪费了时间和精力。

要进行真正的集成测试,您应该使用TestServer来执行类似Register操作的操作。然后,断言用户&#34;发布&#34;到那个动作实际上最终在数据库中。抛弃所有其他无用的代码。