---Build+ GO CREATE TABLE tSQLt.TestResult( Id INT IDENTITY(1,1) PRIMARY KEY CLUSTERED, Class NVARCHAR(MAX) NOT NULL, TestCase NVARCHAR(MAX) NOT NULL, Name AS (QUOTENAME(Class) + '.' + QUOTENAME(TestCase)), TranName NVARCHAR(MAX) NOT NULL, Result NVARCHAR(MAX) NULL, Msg NVARCHAR(MAX) NULL, TestStartTime DATETIME NOT NULL CONSTRAINT [DF:TestResult(TestStartTime)] DEFAULT GETDATE(), TestEndTime DATETIME NULL ); GO CREATE TABLE tSQLt.TestMessage( Msg NVARCHAR(MAX) ); GO CREATE TABLE tSQLt.Run_LastExecution( TestName NVARCHAR(MAX), SessionId INT, LoginTime DATETIME ); GO CREATE TABLE tSQLt.Private_ExpectException(i INT); GO CREATE PROCEDURE tSQLt.Private_Print @Message NVARCHAR(MAX), @Severity INT = 0 AS BEGIN DECLARE @SPos INT;SET @SPos = 1; DECLARE @EPos INT; DECLARE @Len INT; SET @Len = LEN(@Message); DECLARE @SubMsg NVARCHAR(MAX); DECLARE @Cmd NVARCHAR(MAX); DECLARE @CleanedMessage NVARCHAR(MAX); SET @CleanedMessage = REPLACE(@Message,'%','%%'); WHILE (@SPos <= @Len) BEGIN SET @EPos = CHARINDEX(CHAR(13)+CHAR(10),@CleanedMessage+CHAR(13)+CHAR(10),@SPos); SET @SubMsg = SUBSTRING(@CleanedMessage, @SPos, @EPos - @SPos); SET @Cmd = N'RAISERROR(@Msg,@Severity,10) WITH NOWAIT;'; EXEC sp_executesql @Cmd, N'@Msg NVARCHAR(MAX),@Severity INT', @SubMsg, @Severity; SELECT @SPos = @EPos + 2, @Severity = 0; --Print only first line with high severity END RETURN 0; END; GO CREATE PROCEDURE tSQLt.Private_PrintXML @Message XML AS BEGIN SELECT @Message FOR XML PATH('');--Required together with ":XML ON" sqlcmd statement to allow more than 1mb to be returned RETURN 0; END; GO CREATE PROCEDURE tSQLt.GetNewTranName @TranName CHAR(32) OUTPUT AS BEGIN SELECT @TranName = LEFT('tSQLtTran'+REPLACE(CAST(NEWID() AS NVARCHAR(60)),'-',''),32); END; GO CREATE PROCEDURE tSQLt.SetTestResultFormatter @Formatter NVARCHAR(4000) AS BEGIN IF EXISTS (SELECT 1 FROM sys.extended_properties WHERE [name] = N'tSQLt.ResultsFormatter') BEGIN EXEC sp_dropextendedproperty @name = N'tSQLt.ResultsFormatter', @level0type = 'SCHEMA', @level0name = 'tSQLt', @level1type = 'PROCEDURE', @level1name = 'Private_OutputTestResults'; END; EXEC sp_addextendedproperty @name = N'tSQLt.ResultsFormatter', @value = @Formatter, @level0type = 'SCHEMA', @level0name = 'tSQLt', @level1type = 'PROCEDURE', @level1name = 'Private_OutputTestResults'; END; GO CREATE FUNCTION tSQLt.GetTestResultFormatter() RETURNS NVARCHAR(MAX) AS BEGIN DECLARE @FormatterName NVARCHAR(MAX); SELECT @FormatterName = CAST(value AS NVARCHAR(MAX)) FROM sys.extended_properties WHERE name = N'tSQLt.ResultsFormatter' AND major_id = OBJECT_ID('tSQLt.Private_OutputTestResults'); SELECT @FormatterName = COALESCE(@FormatterName, 'tSQLt.DefaultResultFormatter'); RETURN @FormatterName; END; GO CREATE PROCEDURE tSQLt.Private_OutputTestResults @TestResultFormatter NVARCHAR(MAX) = NULL AS BEGIN DECLARE @Formatter NVARCHAR(MAX); SELECT @Formatter = COALESCE(@TestResultFormatter, tSQLt.GetTestResultFormatter()); EXEC (@Formatter); END GO ---------------------------------------------------------------------- CREATE FUNCTION tSQLt.Private_GetLastTestNameIfNotProvided(@TestName NVARCHAR(MAX)) RETURNS NVARCHAR(MAX) AS BEGIN IF(LTRIM(ISNULL(@TestName,'')) = '') BEGIN SELECT @TestName = TestName FROM tSQLt.Run_LastExecution le JOIN sys.dm_exec_sessions es ON le.SessionId = es.session_id AND le.LoginTime = es.login_time WHERE es.session_id = @@SPID; END RETURN @TestName; END GO CREATE PROCEDURE tSQLt.Private_SaveTestNameForSession @TestName NVARCHAR(MAX) AS BEGIN DELETE FROM tSQLt.Run_LastExecution WHERE SessionId = @@SPID; INSERT INTO tSQLt.Run_LastExecution(TestName, SessionId, LoginTime) SELECT TestName = @TestName, session_id, login_time FROM sys.dm_exec_sessions WHERE session_id = @@SPID; END GO ---------------------------------------------------------------------- CREATE VIEW tSQLt.TestClasses AS SELECT s.name AS Name, s.schema_id AS SchemaId FROM sys.extended_properties ep JOIN sys.schemas s ON ep.major_id = s.schema_id WHERE ep.name = N'tSQLt.TestClass'; GO CREATE VIEW tSQLt.Tests AS SELECT classes.SchemaId, classes.Name AS TestClassName, procs.object_id AS ObjectId, procs.name AS Name FROM tSQLt.TestClasses classes JOIN sys.procedures procs ON classes.SchemaId = procs.schema_id WHERE LOWER(procs.name) LIKE 'test%'; GO CREATE FUNCTION tSQLt.TestCaseSummary() RETURNS TABLE AS RETURN WITH A(Cnt, SuccessCnt, FailCnt, ErrorCnt) AS ( SELECT COUNT(1), ISNULL(SUM(CASE WHEN Result = 'Success' THEN 1 ELSE 0 END), 0), ISNULL(SUM(CASE WHEN Result = 'Failure' THEN 1 ELSE 0 END), 0), ISNULL(SUM(CASE WHEN Result = 'Error' THEN 1 ELSE 0 END), 0) FROM tSQLt.TestResult ) SELECT 'Test Case Summary: ' + CAST(Cnt AS NVARCHAR) + ' test case(s) executed, '+ CAST(SuccessCnt AS NVARCHAR) + ' succeeded, '+ CAST(FailCnt AS NVARCHAR) + ' failed, '+ CAST(ErrorCnt AS NVARCHAR) + ' errored.' Msg,* FROM A; GO CREATE PROCEDURE tSQLt.Private_ValidateProcedureCanBeUsedWithSpyProcedure @ProcedureName NVARCHAR(MAX) AS BEGIN IF NOT EXISTS(SELECT 1 FROM sys.procedures WHERE object_id = OBJECT_ID(@ProcedureName)) BEGIN RAISERROR('Cannot use SpyProcedure on %s because the procedure does not exist', 16, 10, @ProcedureName) WITH NOWAIT; END; IF (1020 < (SELECT COUNT(*) FROM sys.parameters WHERE object_id = OBJECT_ID(@ProcedureName))) BEGIN RAISERROR('Cannot use SpyProcedure on procedure %s because it contains more than 1020 parameters', 16, 10, @ProcedureName) WITH NOWAIT; END; END; GO CREATE PROCEDURE tSQLt.AssertEquals @Expected SQL_VARIANT, @Actual SQL_VARIANT, @Message NVARCHAR(MAX) = '' AS BEGIN IF ((@Expected = @Actual) OR (@Actual IS NULL AND @Expected IS NULL)) RETURN 0; DECLARE @Msg NVARCHAR(MAX); SELECT @Msg = 'Expected: <' + ISNULL(CAST(@Expected AS NVARCHAR(MAX)), 'NULL') + '> but was: <' + ISNULL(CAST(@Actual AS NVARCHAR(MAX)), 'NULL') + '>'; IF((COALESCE(@Message,'') <> '') AND (@Message NOT LIKE '% ')) SET @Message = @Message + ' '; EXEC tSQLt.Fail @Message, @Msg; END; GO /*******************************************************************************************/ /*******************************************************************************************/ /*******************************************************************************************/ CREATE FUNCTION tSQLt.Private_GetCleanSchemaName(@SchemaName NVARCHAR(MAX), @ObjectName NVARCHAR(MAX)) RETURNS NVARCHAR(MAX) AS BEGIN RETURN (SELECT SCHEMA_NAME(schema_id) FROM sys.objects WHERE object_id = CASE WHEN ISNULL(@SchemaName,'') in ('','[]') THEN OBJECT_ID(@ObjectName) ELSE OBJECT_ID(@SchemaName + '.' + @ObjectName) END); END; GO CREATE FUNCTION [tSQLt].[Private_GetCleanObjectName](@ObjectName NVARCHAR(MAX)) RETURNS NVARCHAR(MAX) AS BEGIN RETURN (SELECT OBJECT_NAME(OBJECT_ID(@ObjectName))); END; GO CREATE FUNCTION tSQLt.Private_ResolveFakeTableNamesForBackwardCompatibility (@TableName NVARCHAR(MAX), @SchemaName NVARCHAR(MAX)) RETURNS TABLE AS RETURN SELECT QUOTENAME(OBJECT_SCHEMA_NAME(object_id)) AS CleanSchemaName, QUOTENAME(OBJECT_NAME(object_id)) AS CleanTableName FROM (SELECT CASE WHEN @SchemaName IS NULL THEN OBJECT_ID(@TableName) ELSE COALESCE(OBJECT_ID(@SchemaName + '.' + @TableName),OBJECT_ID(@TableName + '.' + @SchemaName)) END object_id ) ids; GO /*******************************************************************************************/ /*******************************************************************************************/ /*******************************************************************************************/ CREATE FUNCTION tSQLt.Private_GetOriginalTableName(@SchemaName NVARCHAR(MAX), @TableName NVARCHAR(MAX)) --DELETE!!! RETURNS NVARCHAR(MAX) AS BEGIN RETURN (SELECT CAST(value AS NVARCHAR(4000)) FROM sys.extended_properties WHERE class_desc = 'OBJECT_OR_COLUMN' AND major_id = OBJECT_ID(@SchemaName + '.' + @TableName) AND minor_id = 0 AND name = 'tSQLt.FakeTable_OrgTableName'); END; GO CREATE FUNCTION tSQLt.Private_GetOriginalTableInfo(@TableObjectId INT) RETURNS TABLE AS RETURN SELECT CAST(value AS NVARCHAR(4000)) OrgTableName, OBJECT_ID(QUOTENAME(OBJECT_SCHEMA_NAME(@TableObjectId)) + '.' + QUOTENAME(CAST(value AS NVARCHAR(4000)))) OrgTableObjectId FROM sys.extended_properties WHERE class_desc = 'OBJECT_OR_COLUMN' AND major_id = @TableObjectId AND minor_id = 0 AND name = 'tSQLt.FakeTable_OrgTableName'; GO CREATE FUNCTION [tSQLt].[F_Num]( @N INT ) RETURNS TABLE AS RETURN WITH C0(c) AS (SELECT 1 UNION ALL SELECT 1), C1(c) AS (SELECT 1 FROM C0 AS A CROSS JOIN C0 AS B), C2(c) AS (SELECT 1 FROM C1 AS A CROSS JOIN C1 AS B), C3(c) AS (SELECT 1 FROM C2 AS A CROSS JOIN C2 AS B), C4(c) AS (SELECT 1 FROM C3 AS A CROSS JOIN C3 AS B), C5(c) AS (SELECT 1 FROM C4 AS A CROSS JOIN C4 AS B), C6(c) AS (SELECT 1 FROM C5 AS A CROSS JOIN C5 AS B) SELECT TOP(CASE WHEN @N>0 THEN @N ELSE 0 END) ROW_NUMBER() OVER (ORDER BY c) no FROM C6; GO CREATE PROCEDURE [tSQLt].[Private_SetFakeViewOn_SingleView] @ViewName NVARCHAR(MAX) AS BEGIN DECLARE @Cmd NVARCHAR(MAX), @SchemaName NVARCHAR(MAX), @TriggerName NVARCHAR(MAX); SELECT @SchemaName = OBJECT_SCHEMA_NAME(ObjId), @ViewName = OBJECT_NAME(ObjId), @TriggerName = OBJECT_NAME(ObjId) + '_SetFakeViewOn' FROM (SELECT OBJECT_ID(@ViewName) AS ObjId) X; SET @Cmd = 'CREATE TRIGGER $$SCHEMA_NAME$$.$$TRIGGER_NAME$$ ON $$SCHEMA_NAME$$.$$VIEW_NAME$$ INSTEAD OF INSERT AS BEGIN RAISERROR(''Test system is in an invalid state. SetFakeViewOff must be called if SetFakeViewOn was called. Call SetFakeViewOff after creating all test case procedures.'', 16, 10) WITH NOWAIT; RETURN; END; '; SET @Cmd = REPLACE(@Cmd, '$$SCHEMA_NAME$$', QUOTENAME(@SchemaName)); SET @Cmd = REPLACE(@Cmd, '$$VIEW_NAME$$', QUOTENAME(@ViewName)); SET @Cmd = REPLACE(@Cmd, '$$TRIGGER_NAME$$', QUOTENAME(@TriggerName)); EXEC(@Cmd); EXEC sp_addextendedproperty @name = N'SetFakeViewOnTrigger', @value = 1, @level0type = 'SCHEMA', @level0name = @SchemaName, @level1type = 'VIEW', @level1name = @ViewName, @level2type = 'TRIGGER', @level2name = @TriggerName; RETURN 0; END; GO CREATE PROCEDURE [tSQLt].[SetFakeViewOn] @SchemaName NVARCHAR(MAX) AS BEGIN DECLARE @ViewName NVARCHAR(MAX); DECLARE viewNames CURSOR LOCAL FAST_FORWARD FOR SELECT QUOTENAME(OBJECT_SCHEMA_NAME(object_id)) + '.' + QUOTENAME([name]) AS viewName FROM sys.views WHERE schema_id = SCHEMA_ID(@SchemaName); OPEN viewNames; FETCH NEXT FROM viewNames INTO @ViewName; WHILE @@FETCH_STATUS = 0 BEGIN EXEC tSQLt.Private_SetFakeViewOn_SingleView @ViewName; FETCH NEXT FROM viewNames INTO @ViewName; END; CLOSE viewNames; DEALLOCATE viewNames; END; GO CREATE PROCEDURE [tSQLt].[Private_SetFakeViewOff_SingleView] @ViewName NVARCHAR(MAX) AS BEGIN DECLARE @Cmd NVARCHAR(MAX), @SchemaName NVARCHAR(MAX), @TriggerName NVARCHAR(MAX); SELECT @SchemaName = QUOTENAME(OBJECT_SCHEMA_NAME(ObjId)), @TriggerName = QUOTENAME(OBJECT_NAME(ObjId) + '_SetFakeViewOn') FROM (SELECT OBJECT_ID(@ViewName) AS ObjId) X; SET @Cmd = 'DROP TRIGGER %SCHEMA_NAME%.%TRIGGER_NAME%;'; SET @Cmd = REPLACE(@Cmd, '%SCHEMA_NAME%', @SchemaName); SET @Cmd = REPLACE(@Cmd, '%TRIGGER_NAME%', @TriggerName); EXEC(@Cmd); END; GO CREATE PROCEDURE [tSQLt].[SetFakeViewOff] @SchemaName NVARCHAR(MAX) AS BEGIN DECLARE @ViewName NVARCHAR(MAX); DECLARE viewNames CURSOR LOCAL FAST_FORWARD FOR SELECT QUOTENAME(OBJECT_SCHEMA_NAME(t.parent_id)) + '.' + QUOTENAME(OBJECT_NAME(t.parent_id)) AS viewName FROM sys.extended_properties ep JOIN sys.triggers t on ep.major_id = t.object_id WHERE ep.name = N'SetFakeViewOnTrigger' OPEN viewNames; FETCH NEXT FROM viewNames INTO @ViewName; WHILE @@FETCH_STATUS = 0 BEGIN EXEC tSQLt.Private_SetFakeViewOff_SingleView @ViewName; FETCH NEXT FROM viewNames INTO @ViewName; END; CLOSE viewNames; DEALLOCATE viewNames; END; GO CREATE FUNCTION tSQLt.Private_GetQuotedFullName(@Objectid INT) RETURNS NVARCHAR(517) AS BEGIN DECLARE @QuotedName NVARCHAR(517); SELECT @QuotedName = QUOTENAME(OBJECT_SCHEMA_NAME(@Objectid)) + '.' + QUOTENAME(OBJECT_NAME(@Objectid)); RETURN @QuotedName; END; GO CREATE FUNCTION tSQLt.Private_GetSchemaId(@SchemaName NVARCHAR(MAX)) RETURNS INT AS BEGIN RETURN ( SELECT TOP(1) schema_id FROM sys.schemas WHERE @SchemaName IN (name, QUOTENAME(name), QUOTENAME(name, '"')) ORDER BY CASE WHEN name = @SchemaName THEN 0 ELSE 1 END ); END; GO CREATE FUNCTION tSQLt.Private_IsTestClass(@TestClassName NVARCHAR(MAX)) RETURNS BIT AS BEGIN RETURN CASE WHEN EXISTS( SELECT 1 FROM tSQLt.TestClasses WHERE SchemaId = tSQLt.Private_GetSchemaId(@TestClassName) ) THEN 1 ELSE 0 END; END; GO CREATE FUNCTION tSQLt.Private_ResolveSchemaName(@Name NVARCHAR(MAX)) RETURNS TABLE AS RETURN WITH ids(schemaId) AS (SELECT tSQLt.Private_GetSchemaId(@Name) ), idsWithNames(schemaId, quotedSchemaName) AS (SELECT schemaId, QUOTENAME(SCHEMA_NAME(schemaId)) FROM ids ) SELECT schemaId, quotedSchemaName, CASE WHEN EXISTS(SELECT 1 FROM tSQLt.TestClasses WHERE TestClasses.SchemaId = idsWithNames.schemaId) THEN 1 ELSE 0 END AS isTestClass, CASE WHEN schemaId IS NOT NULL THEN 1 ELSE 0 END AS isSchema FROM idsWithNames; GO CREATE FUNCTION tSQLt.Private_ResolveObjectName(@Name NVARCHAR(MAX)) RETURNS TABLE AS RETURN WITH ids(schemaId, objectId) AS (SELECT SCHEMA_ID(OBJECT_SCHEMA_NAME(OBJECT_ID(@Name))), OBJECT_ID(@Name) ), idsWithNames(schemaId, objectId, quotedSchemaName, quotedObjectName) AS (SELECT schemaId, objectId, QUOTENAME(SCHEMA_NAME(schemaId)) AS quotedSchemaName, QUOTENAME(OBJECT_NAME(objectId)) AS quotedObjectName FROM ids ) SELECT schemaId, objectId, quotedSchemaName, quotedObjectName, quotedSchemaName + '.' + quotedObjectName AS quotedFullName, CASE WHEN LOWER(quotedObjectName) LIKE '[[]test%]' AND objectId = OBJECT_ID(quotedSchemaName + '.' + quotedObjectName,'P') THEN 1 ELSE 0 END AS isTestCase FROM idsWithNames; GO CREATE FUNCTION tSQLt.Private_ResolveName(@Name NVARCHAR(MAX)) RETURNS TABLE AS RETURN WITH resolvedNames(ord, schemaId, objectId, quotedSchemaName, quotedObjectName, quotedFullName, isTestClass, isTestCase, isSchema) AS (SELECT 1, schemaId, NULL, quotedSchemaName, NULL, quotedSchemaName, isTestClass, 0, 1 FROM tSQLt.Private_ResolveSchemaName(@Name) UNION ALL SELECT 2, schemaId, objectId, quotedSchemaName, quotedObjectName, quotedFullName, 0, isTestCase, 0 FROM tSQLt.Private_ResolveObjectName(@Name) UNION ALL SELECT 3, NULL, NULL, NULL, NULL, NULL, 0, 0, 0 ) SELECT TOP(1) schemaId, objectId, quotedSchemaName, quotedObjectName, quotedFullName, isTestClass, isTestCase, isSchema FROM resolvedNames WHERE schemaId IS NOT NULL OR ord = 3 ORDER BY ord GO CREATE PROCEDURE tSQLt.Uninstall AS BEGIN DROP TYPE tSQLt.Private; EXEC tSQLt.DropClass 'tSQLt'; DROP ASSEMBLY tSQLtCLR; END; GO