sqlsql-servertsqlt

Using tSQLt AssertEqualsTableSchema in a test procedure that contains TRY/CATCH


It seems that the SQL test framework tSQLt does not allow to use an assertion such as tSQLt.AssertEqualsTableSchema after a TRY/CATCH block that generates an error. If I try that, the test generates an error instead of a pass/fail result. On the other hand, a simpler assertion such as tSQLt.AssertEquals works in such a case, passing the test.

I would like to know if there is a way to use such assert satements in this case, or if it is a fundamental limitation of tSQLt and it does not work.

This code reproduces the problem:

EXEC tSQLt.NewTestClass @ClassName = N'errtest';
GO

CREATE OR ALTER PROCEDURE errtest.test_simple
AS
BEGIN
    
    PRINT('Executing errtest.test_simple');

    CREATE TABLE Actual (
        A INT,
        B NVARCHAR(10));

    CREATE TABLE Expected (
        A INT,
        B NVARCHAR(10));

    BEGIN TRY
        DECLARE @IntegerVariable AS INT;
        SET @IntegerVariable = 'a string';
        PRINT('There were no errors.');
    END TRY
    BEGIN CATCH
        PRINT('An error happened.');
    END CATCH

    DECLARE @AssertType NVARCHAR(100);
    SET @AssertType = 'AssertAssertEqualsTableSchema';
    --SET @AssertType = 'AssertEquals';

    IF @AssertType = 'AssertEquals' BEGIN
        PRINT('ASSERT: Equals for 2 INTs');
        EXEC tSQLt.AssertEquals @Expected = 1
                               ,@Actual = 1
                               ,@Message = N'no'
    END 
    ELSE BEGIN
        PRINT('ASSERT: EqualsTableSchema');
        EXEC tSQLt.AssertEqualsTableSchema @Expected = N'Expected'
                                          ,@Actual = N'Actual'
                                          ,@Message = N'Hallo'
    END

END
GO


EXEC tSQLt.Run 'errtest.test_simple';

If this is run with tSQLt installed, I get following result:

Executing errtest.test_simple
An error happened.
ASSERT: EqualsTableSchema
[errtest].[test_simple] failed: (Error) The current transaction cannot be committed and cannot support operations that write to the log file. Roll back the transaction.[16,1]{tSQLt.AssertEqualsTableSchema,7} (There was also a ROLLBACK ERROR --> The current transaction cannot be committed and cannot be rolled back to a savepoint. Roll back the entire transaction.{tSQLt.Private_RunTest,187})
 
+----------------------+
|Test Execution Summary|
+----------------------+
 
|No|Test Case Name         |Dur(ms)|Result|
+--+-----------------------+-------+------+
|1 |[errtest].[test_simple]|      3|Error |
----------------------------------------------------------------------------------------
Msg 50000, Level 16, State 10, Line 47
Test Case Summary: 1 test case(s) executed, 0 succeeded, 0 skipped, 0 failed, 1 errored.
----------------------------------------------------------------------------------------

However, if I uncomment the line --SET @AssertType = 'AssertEquals';, the simpler assertion tSQLt.AssertEquals runs without errors and passes the test:

Executing errtest.test_simple
An error happened.
ASSERT: Equals for 2 INTs
 
+----------------------+
|Test Execution Summary|
+----------------------+
 
|No|Test Case Name         |Dur(ms)|Result |
+--+-----------------------+-------+-------+
|1 |[errtest].[test_simple]|      7|Success|
----------------------------------------------------------------------------------------
Test Case Summary: 1 test case(s) executed, 1 succeeded, 0 skipped, 0 failed, 0 errored.
----------------------------------------------------------------------------------------

Here is a list of which assertion works and which do not work in the above example.

Working: AssertEquals, AssertEqualsString, AssertNotEquals, AssertObjectDoesNotExist, AssertObjectExists, Fail, (no assert used) → test passes

Not working: AssertEqualsTableSchema, AssertEmptyTable, AssertEqualsTable, AssertLike, AssertResultSetsHaveSameMetaData


Solution

  • The problem is that tSQLt starts a transaction - this is needed so faking objects can be rolled back and any helper objects it creates are removed. But errors in try catch can doom the transaction. tSQLt.AssertEqualsTableSchema attempts to insert to tables tSQLt.Private_AssertEqualsTableSchema_Expected and tSQLt.Private_AssertEqualsTableSchema_Actual which would write to the log file hence the error.

    As a workaround you can do your own check in a manner that does not touch the transaction log. Example below (based on the SELECT that tSQLt does)

      IF EXISTS (SELECT *
                 FROM   (
                          SELECT 
                              C.object_id,
                              RANK()OVER(PARTITION BY C.object_id ORDER BY C.column_id) AS col_ordinal,
                              C.name,
                              CAST(C.system_type_id AS NVARCHAR(MAX))+QUOTENAME(TS.name) system_type_id,
                              CAST(C.user_type_id AS NVARCHAR(MAX))+CASE WHEN TU.system_type_id<> TU.user_type_id THEN QUOTENAME(SCHEMA_NAME(TU.schema_id))+'.' ELSE '' END + QUOTENAME(TU.name) user_type_id,
                              C.max_length,
                              C.precision,
                              C.scale,
                              C.collation_name,
                              C.is_nullable
                            FROM sys.columns AS C
                            JOIN sys.types AS TS
                              ON C.system_type_id = TS.user_type_id
                            JOIN sys.types AS TU
                              ON C.user_type_id = TU.user_type_id
                           WHERE C.object_id IN ( OBJECT_ID('dbo.Expected'), OBJECT_ID('dbo.Actual'))
                         ) T
                         GROUP BY col_ordinal, name, system_type_id, user_type_id, max_length, precision, scale, collation_name, is_nullable
                         HAVING COUNT(*) = 1
                 )
      EXEC [tSQLt].Fail 'Unexpected/missing column(s)' 
    

    Obviously the above could be made more elaborate to return more details of specific differences and itself be moved into a stored proc to facilitate re-use.