// ReSharper disable once UnusedMember.Global public DbTestFixture() { this.dbTestSetup = DbTestSetupFinder.DbTestSetup; if (dbTestSetup == null) { throw new Exception("Failed to create test fixture. Unable to determine DbTest setup class."); } this.ServiceProvider = dbTestSetup.ProduceServiceProvider(); this.config = ServiceProvider.GetService <IDbTestRunnerConfiguration>(); this.scaffolder = ServiceProvider.GetService <IDbScaffolder>(); this.context = ServiceProvider.GetService <IDbTestRunnerContext>(); this.context.ProduceSqlParametersForCustomTypes = dbTestSetup.ProduceSqlParametersForCustomTypes; // if we're not supposed to run DbTests, then don't bother setting up the runner. if (!this.config.Enabled()) { return; } // ensure the db exists, has latest migrations, etc. var initializationResult = InitializeDatabase(); if (!initializationResult.IsSuccessful) { throw new DbTestException("Failed to intialize database for tests."); } }
private DbTestSqlResult ExecuteAct(IDbTestRunnerContext context, DbTestRunnerResult result, IDisposable dbHandle) { var request = OnAct(context, dbHandle, this.TestOutputHelper); result.TestPhase = "ACT"; return(ExecuteTestPhase(result, "ACT", request)); }
protected override void RegisterServices(IServiceCollection services, IDbTestRunnerContext context, IDbTestRunnerConfiguration config, IDbScaffolder scaffolder) { var efScaffolder = scaffolder as EfDbScaffolder; var efConfig = config as DbTestRunnerConfiguration; efScaffolder?.RegisterServices <TDataContext>(services, context, efConfig); }
protected SqlParameter[] GetParametersForDbObject(string objectName, IDbTestRunnerContext runnerContext) { var parameters = new List <SqlParameter>(); var sql = @" Select p.name, t.name From sys.parameters p inner join sys.types t on p.system_type_id = t.system_type_id where Object_Id = OBJECT_ID(@objname) order by p.parameter_id"; var pObjName = new SqlParameter("@objname", objectName); using (var cmd = GetDbConnection().CreateCommand()) { if (cmd.Connection.State != ConnectionState.Open) { cmd.Connection.Open(); } cmd.CommandType = CommandType.Text; cmd.CommandText = sql; cmd.Parameters.Add(pObjName); using (var rdr = cmd.ExecuteReader()) { while (rdr.Read()) { var parameterName = rdr[0].ToString(); var parameterType = rdr[1].ToString(); parameters.Add(this.GetSqlParameterWithDefaultValue(parameterName, parameterType, runnerContext)); } } } return(parameters.ToArray()); }
/// <summary> /// Drops the database specified in the context's DBName property /// </summary> public void DropDatabase(IDbTestRunnerContext context, IDbTestRunnerConfiguration config) { if (context == null) { return; } try { var connString = ProduceConnectionString(config, "master"); using (var conn = new SqlConnection(connString)) { conn.Open(); using (var cmd = conn.CreateCommand()) { cmd.CommandType = CommandType.Text; cmd.CommandText = $"Drop Database {context.DbName}"; cmd.ExecuteNonQuery(); } } } catch (Exception e) { Console.WriteLine(e); throw; } }
public override void TearDownDatabase(IDbTestRunnerContext context, IDbTestRunnerConfiguration config, bool allTestsWereSuccessful) { var sqlConnection = new SqlConnection(context.ConnectionString); SqlConnection.ClearPool(sqlConnection); if ((allTestsWereSuccessful && config.DropDatabaseOnSuccess) || (!allTestsWereSuccessful && config.DropDatabaseOnFailure)) { context.DbScaffolder.DropDatabase(context, config); } }
/// <summary> /// Executes all the EF data context migrations against the configured database /// </summary> private void EnsureMigrations(DbTestRunnerConfiguration config, IDbTestRunnerContext context, DbInitializationResult result) { try { using (var scope = context.ServiceProvider.CreateScope()) { var migrationRunner = scope.ServiceProvider.GetService <DataMigrationRunner>(); migrationRunner.ApplyMigrations(config.DataContextType); result.Logs.Add("DB Migrations completed."); result.IsSuccessful = true; } } catch (Exception e) { result.Logs.Add($"Encountered exception running migrations. Exception: {e.Message} Details: {e}"); throw; } }
/// <summary> /// Creates the database specified in Context.DbName /// </summary> protected void CreateDatabase(IDbTestRunnerConfiguration config, IDbTestRunnerContext context) { var connStr = this.ProduceConnectionString(config, "master"); string sql = $"CREATE DATABASE {context.DbName}"; using (var conn = new SqlConnection(connStr)) { conn.Open(); using (var cmd = conn.CreateCommand()) { cmd.CommandText = sql; cmd.CommandType = CommandType.Text; cmd.ExecuteNonQuery(); } conn.Close(); } }
public override void TearDownDatabase(IDbTestRunnerContext context, IDbTestRunnerConfiguration config, bool allTestsWereSuccessful) { if (!(config is DbTestRunnerConfiguration efConfig)) { return; } // clears the connection pool for the specified connection // this lets us drop the database without having hanging active connections using (var dataContext = context.ProduceDbContext(efConfig.DataContextType)) { if (dataContext.Database.GetDbConnection() is SqlConnection sqlConnection) { SqlConnection.ClearPool(sqlConnection); } } if ((allTestsWereSuccessful && config.DropDatabaseOnSuccess) || (!allTestsWereSuccessful && config.DropDatabaseOnFailure)) { context.DbScaffolder.DropDatabase(context, config); } }
/// <summary> /// Invokes the ASSERT hook method /// </summary> // ReSharper disable once UnusedParameter.Local private void ExecuteAssert(IDbTestRunnerContext context, DbTestRunnerResult result, List <DataTable> data, IDisposable dbHandle) { try { result.TestPhase = "ASSERT"; result.IsSuccessful = true; result.Logs.Add("ASSERT Phase: Running test Assertions."); this.OnAssert(data == null || !data.Any() ? null : data[0], dbHandle, data?.ToArray(), this.TestOutputHelper); result.Logs.Add("ASSERT Phase: All assertions passed."); } catch (XunitException) { // we actually just want to re-throw the current exception to maintain the stack trace throw; } catch (Exception e) { // non-assertion exception occurred. result.Logs.Add($"Error asserting state of data. {e}"); result.Exception = e; result.IsSuccessful = false; } }
/// <summary> /// Configures all the services necessary to produce the data context from the IoC container. /// </summary> private void ConfigureDataContext <TDataContext>(IServiceCollection services, IDbTestRunnerContext context, DbTestRunnerConfiguration config) where TDataContext : DbContext { // Our data contexts require loggers, so add the loggers to the services collection services.AddLogging(); // if a db name is specified, use that, otherwise generate a unique database name context.DbName = context.DbName ?? config.DbName ?? ProduceUniqueDatabaseName <TDataContext>(); // build an ado.net connection string for the server and database configured. // turn off connection pooling to prevent it from holding on to the connection after we're done using it. context.ConnectionString = ProduceConnectionString(config, context.DbName, useConnectionPooling: false); var connectionString = context.ConnectionString; services.AddDbContext <TDataContext>(options => { options.UseSqlServer(connectionString); }); // register the data migration runner. services.AddDataMigrationRunner(); }
protected virtual SqlParameter ProduceSqlParametersForCustomTypes(string name, string type, IDbTestRunnerContext runnerContext) { return(runnerContext.ProduceSqlParametersForCustomTypes?.Invoke(name, type)); }
private SqlParameter GetSqlParameterWithDefaultValue(string name, string type, IDbTestRunnerContext runnerContext) { switch (type) { case "bit": return(new SqlParameter(name, false)); case "int": case "byte": case "tinyint": case "smallint": case "bigint": case "numeric": return(new SqlParameter(name, -1)); case "decimal": case "float": case "smallmoney": return(new SqlParameter(name, -1m)); case "date": case "datetime": case "datetime2": case "smalldatetime": case "timestamp": return(new SqlParameter(name, System.Data.SqlTypes.SqlDateTime.MinValue.Value.AddSeconds(1))); case "text": case "ntext": case "varchar": case "nvarchar": case "char": case "nchar": case "xml": return(new SqlParameter(name, "")); default: { var customTypeParameter = ProduceSqlParametersForCustomTypes(name, type, runnerContext); return(customTypeParameter ?? new SqlParameter(name, null)); } } }
protected override void InitializeSchemaAndData(IDbTestRunnerConfiguration config, IDbTestRunnerContext context, DbInitializationResult result) { if (!(config is DbTestRunnerConfiguration efConfig)) { result.IsSuccessful = false; result.Logs.Add($"Failed to initialize schema and database. Configuration is not of the expected type. Expected: {typeof(DbTestRunnerConfiguration).FullName} actual: {config.GetType().FullName}"); return; } result.Logs.Add($"Preparing Database with {efConfig.DataContextType} migrations."); EnsureMigrations(efConfig, context, result); }
/// <summary> /// Return the sql text to execute as part of the ACT phase of the test. /// Or you may modify the data using the provided connection /// </summary> /// <returns>the sql text to execute</returns> protected abstract SqlRequest Act(IDbTestRunnerContext context, SqlConnection connection, ITestOutputHelper testOutputHelper);
protected override SqlRequest Act(IDbTestRunnerContext context, TDataContext dataContext, ITestOutputHelper testOutputHelper) { var functionParameters = GetParametersForDbObject(this.FunctionName, context); return(this.InvokeTableFunction(this.FunctionName, this.SchemaName, functionParameters)); }
/// <summary> /// ACT on the state of the data. /// This is where you should exercise the db object under test. /// The data returned from this code will be presented to the Assert method. /// </summary> protected abstract SqlRequest OnAct(IDbTestRunnerContext context, IDisposable dbHandle, ITestOutputHelper testOutputHelper);
protected override SqlRequest OnAct(IDbTestRunnerContext context, IDisposable dbHandle, ITestOutputHelper testOutputHelper) { return(Act(context, ToDataContext(dbHandle), testOutputHelper)); }
protected override SqlRequest OnArrange(IDbTestRunnerContext context, IDisposable dbHandle, ITestOutputHelper testOutputHelper) { return(Arrange(context, ToSqlConnection(dbHandle), testOutputHelper)); }
public void RegisterDataContext(IServiceCollection services, IDbTestRunnerContext context, DbTestRunnerConfiguration config, IEfDbScaffolder scaffolder) { scaffolder.RegisterServices <TDataContext>(services, context, config); }
public static DbContext ProduceDbContext(this IDbTestRunnerContext context, Type dataContextType) { return(context.ServiceProvider.CreateScope().ServiceProvider.GetService(dataContextType) as DbContext); }
protected virtual void InitializeSchemaAndData(IDbTestRunnerConfiguration config, IDbTestRunnerContext context, DbInitializationResult result) { result.IsSuccessful = true; }
protected override void InitializeSchemaAndData(IDbTestRunnerConfiguration config, IDbTestRunnerContext context, DbInitializationResult result) { if (!(config is DbTestRunnerConfiguration adoConfig)) { result.IsSuccessful = false; result.Logs.Add($"Failed to initialize schema and database. Configuration is not of the expected type. Expected: {typeof(DbTestRunnerConfiguration).FullName} actual: {config.GetType().FullName}"); return; } var connStr = this.ProduceConnectionString(config, context.DbName); if (string.IsNullOrEmpty(connStr)) { result.IsSuccessful = false; result.Logs.Add("Failed to initialize schema and database. Configuration string is empty or null."); return; } result.Logs.Add($"Attempting to initialize DB Schema and Data using connection string: {connStr}"); result.IsSuccessful = adoConfig.InitializeDbSchemaAndData(connStr, result.WasDatabaseCreated); }
protected override SqlRequest Act(IDbTestRunnerContext context, SqlConnection connection, ITestOutputHelper testOutputHelper) { // return just an empty data set so we can examine the shape of the return object. return(SqlRequest.ReturnNoRows(this.GetQualifiedTableOrViewName <TEntity>(this.TableOrViewName, this.Schema))); }
public static TDataContext ProduceDataContext <TDataContext>(this IDbTestRunnerContext context) where TDataContext : DbContext { return((TDataContext)ProduceDbContext(context, typeof(TDataContext))); }
/// <summary> /// Provisions the database specified in Context.DbName /// Creates the DB if it does not exist and runs all /// data context migrations against the database. /// </summary> public DbInitializationResult InitializeDb(IDbTestRunnerConfiguration config, IDbTestRunnerContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (config == null) { throw new ArgumentNullException(nameof(config)); } var result = new DbInitializationResult(); try { if (string.IsNullOrEmpty(context.DbName) || !DoesDatabaseExist(config, context.DbName)) { result.Logs.Add($"Creating new Test database {context.DbName}."); CreateDatabase(config, context); result.WasDatabaseCreated = true; } else { result.Logs.Add($"Using existing database {context.DbName}."); } try { context.ConnectionString = ProduceConnectionString(config, context.DbName); InitializeSchemaAndData(config, context, result); if (!result.IsSuccessful) { result.Logs.Add("Failed to initialize db schema and data."); result.IsSuccessful = false; } } catch (Exception e) { result.Exception = e; result.Logs.Add("Failed to initialize db schema and data."); result.IsSuccessful = false; } if (result.IsSuccessful) { result.Logs.Add($"Database {context.DbName} has been prepared for tests."); result.IsSuccessful = true; } } catch (Exception e) { result.Exception = e; result.Logs.Add($"Error attempting to Initialize DB. {e}"); result.IsSuccessful = false; } return(result); }
/// <summary> /// (hook) /// Return the sql text to execute as part of the ACT phase of the test. /// Or you may modify the data context directly. /// </summary> /// <returns>the sql text to execute</returns> protected abstract SqlRequest Act(IDbTestRunnerContext context, TDataContext dataContext, ITestOutputHelper testOutputHelper);
protected override SqlRequest Arrange(IDbTestRunnerContext context, SqlConnection connection, ITestOutputHelper testOutputHelper) { return(this.DoNothing()); }
protected override SqlRequest Act(IDbTestRunnerContext context, SqlConnection connection, ITestOutputHelper testOutputHelper) { var parameters = this.GetParametersForDbObject(this.StoredProcedureName, context); return(this.ExecuteStoredProcedure(this.DbObjectName, this.SchemaName, parameters)); }
/// <summary> /// Participates in the registration of services /// during DbTestRunner startup. /// /// Provides an opportunity to inject data related /// services into the IoC container. /// </summary> public void RegisterServices <TDataContext>(IServiceCollection services, IDbTestRunnerContext context, DbTestRunnerConfiguration config) where TDataContext : DbContext { // produce and register the data context. ConfigureDataContext <TDataContext>(services, context, config); }