Example #1
0
 /// <summary>
 /// Initializes the test context with necessary objects for each test class.
 /// Should be called from test class constructor.
 /// </summary>
 /// <example>
 /// public class MyTests : DatabaseTestsBase
 /// {
 ///     public MyTests(ITestOutputHelper outputHelper, SqlDatabaseFixture testFixture)
 ///     {
 ///         this.InitializeTestContext(outputHelper, testFixture);
 ///     }
 ///   // Here goes tests
 /// }
 /// </example>
 /// <param name="helper">The XUit output helper (like Console.Write).</param>
 /// <param name="fixture">The database tests fixture.</param>
 protected void InitializeTestContext(ITestOutputHelper helper, SqlDatabaseFixture fixture)
 {
     this.TestFixture = fixture;
     this.TestFixture.SqlConnection = this.GetSqlConnectionString();
     this.TestFixture.InstantiateDatabaseObjects(helper);
     if (!this.TestFixture.IsDatabasePrepared)
     {
         this.PrepareDatabase();
         this.TestFixture.IsDatabasePrepared = true;
     }
 }
        /// <summary>
        /// Compares the database column descriptions with data contract properties
        /// and reports any misalignment (missing columns, missing properties, incompatible types used, null-ability for both).
        /// </summary>
        /// <typeparam name="T">Type of data contract (DB POCO class).</typeparam>
        /// <param name="fixture">The database tests fixture for getting database object columns metadata.</param>
        /// <param name="tableName">Name of the table. Default (if not used) = data contract name.</param>
        /// <param name="exceptPocoProperties">The exceptions to be used for data contract properties (extra properties, which should not be considered). When not specified - uses all found properties.</param>
        /// <param name="exceptDatabaseFields">The exceptions to database column names (extra fields not used to map data to data contract). When not specified - uses all found columns in database object.</param>
        public static async Task <List <string> > CompareDatabaseWithContract <T>(
            SqlDatabaseFixture fixture,
            string tableName = null,
            HashSet <string> exceptPocoProperties = null,
            HashSet <string> exceptDatabaseFields = null)
        {
            tableName ??= typeof(T).Name;
            exceptPocoProperties ??= new HashSet <string>();
            exceptDatabaseFields ??= new HashSet <string>();
            var validationResults = new List <string>();

            List <PocoPropertyMetadata> pocoProperties = GetPocoObjectProperties <T>();
            var databaseFields = (await fixture.Db.QueryAsync(new DatabaseObjectColumnsMetadataQuery(tableName))).ToList();

            // Validating that all DTO POCO properties has counterpart in database object fields
            foreach (PocoPropertyMetadata pocoProperty in pocoProperties)
            {
                if (exceptPocoProperties.Contains(pocoProperty.Name))
                {
                    continue;
                }

                DatabaseObjectColumnMetadata?dbField = databaseFields.FirstOrDefault(dbf =>
                                                                                     dbf.ColumnName.Equals(pocoProperty.Name, StringComparison.OrdinalIgnoreCase));
                if (dbField == null)
                {
                    validationResults.Add($"DB column with name \"{pocoProperty.Name}\" was not found (but property exists). If property is extra (non-database loadable), then use \"exceptPocoProperties\" parameter to exclude it from test.");
                    continue;
                }

                if (pocoProperty.TypeName.Equals("string", StringComparison.OrdinalIgnoreCase) && !(dbField.DataType.Contains("CHAR") || dbField.DataType.Contains("XML")))
                {
                    validationResults.Add($"Data contract property \"{pocoProperty.Name}\" (STRING) has non-matching type \"{dbField.DataType}\" in database column.");
                    continue;
                }

                if (!new HashSet <string> {
                    "BYTE[]", "STRING"
                }.Contains(pocoProperty.TypeName.ToUpper(CultureInfo.InvariantCulture)) && pocoProperty.IsNullable != dbField.IsNullable && pocoProperty.IsNullable && !dbField.HasDefaultValue)
                {
                    validationResults.Add($"Data contract property \"{pocoProperty.Name}\" ({pocoProperty.TypeName}{(pocoProperty.IsNullable ? "?" : string.Empty)}) has non-matching NULL-ability in database field \"{dbField.DataType} {(dbField.IsNullable ? "NULL" : "NOT NULL")}\".");
                }
            }

            // Validating that all Database Fields has counterpart in POCO properties
            foreach (DatabaseObjectColumnMetadata dbField in databaseFields)
            {
                if (exceptDatabaseFields.Contains(dbField.ColumnName))
                {
                    continue;
                }

                PocoPropertyMetadata?pocoProperty = pocoProperties.FirstOrDefault(pp =>
                                                                                  pp.Name.Equals(dbField.ColumnName, StringComparison.OrdinalIgnoreCase));
                if (pocoProperty == null)
                {
                    validationResults.Add($"Data contract property with name \"{dbField.ColumnName}\" was not found (but DB column exists). If DB field is not loadable, then use \"exceptDatabaseFields\" parameter to exclude it from test.");
                    continue;
                }

                if (dbField.DataType.Contains("CHAR") && pocoProperty.TypeName.ToUpper(CultureInfo.InvariantCulture) != "STRING")
                {
                    validationResults.Add($"Database column \"{dbField.ColumnName}\" ({dbField.DataType}) has non-matching type \"{pocoProperty.TypeName}\" in data contract property.");
                    continue;
                }

                if (!new HashSet <string> {
                    "BYTE[]", "STRING"
                }.Contains(pocoProperty.TypeName.ToUpper(CultureInfo.InvariantCulture)) && !dbField.DataType.ToUpper(CultureInfo.InvariantCulture).Contains("XML") && pocoProperty.IsNullable != dbField.IsNullable && pocoProperty.IsNullable && !dbField.HasDefaultValue)
                {
                    validationResults.Add($"Database column \"{dbField.ColumnName}\" ({dbField.DataType} {(dbField.IsNullable ? "NULL" : "NOT NULL")}) has non-matching NULL-ability in data contract property ({pocoProperty.TypeName}{(pocoProperty.IsNullable ? "?" : string.Empty)}).");
                }
            }

            return(validationResults);
        }