简体   繁体   English

如何在StoredProcedure中模拟/伪造RaiseError

[英]How do I mock/fake a RaiseError with in a StoredProcedure

This is my first day with tsqlt so you can expect some vague statements. 这是我使用tsqlt第一天,所以你可以期待一些模糊的陈述。

I am trying to test a storedProcedure which has a Try Catch Block but the actual statements in test are insert and update command. 我正在尝试测试一个带有Try Catch Block的storedProcedure,但测试中的实际语句是insert和update命令。

now I want to test if in case there was an ErrorRaised does my catch block performs the expected tasks. 现在我想测试是否存在ErrorRaised我的catch块是否执行预期的任务。

Can you please guide me on how do I Raise an Error from a stored procedure in test where we do not have anything to mock/fake inside. 你能否指导我如何从测试中的存储过程中提出错误,我们没有任何内容模拟/伪造。

Hope my question is understandable, happy to clarify if needed. 希望我的问题是可以理解的,如果需要,我很乐意澄清。

So if I understand your question correctly, you are trying to test that your catch block works? 因此,如果我正确理解您的问题,您是否正在尝试测试您的catch块是否有效?

The way to do this will depend on what happens within your catch block. 执行此操作的方法取决于catch块中发生的情况。 Imagine this scenario: 想象一下这种情况:

create table mySimpleTable
(
  Id int not null primary key
, StringVar varchar(8) null 
, IntVar tinyint null
)
go

We have a stored procedure that inserts data into this table thus. 我们有一个存储过程,可以将数据插入此表中。

This is based on a template that I use in many of my procedures. 这基于我在许多程序中使用的模板。 It starts by validating the input(s), then does the work it needs to do. 它首先验证输入,然后完成它需要做的工作。 Naming each step can be particularly useful to understand where the error occurred in more complex, multi-step procedures. 命名每个步骤对于了解更复杂的多步骤过程中错误发生的位置特别有用。 The catch block uses my Log4TSql logging framework that you can read more about on my blog and download from SourceForge . catch块使用我的Log4TSql日志框架,您可以在我的博客上阅读更多信息并从SourceForge下载。

The pattern I follow is to capture information about the exception and what the procedure was doing at the time of the error within the catch block but ensure that an error is still thrown at the end of the procedure. 我遵循的模式是捕获有关异常的信息以及catch块中错误发生时过程正在执行的操作,但确保在过程结束时仍然抛出错误。 You could also choose to call raiserror (also throw on SQL2012) within the catch block. 您也可以选择在catch块中调用raiserror(也throw SQL2012)。 Either way, I believe that if a procedure hits an exception it should always be notified up the chain (ie never hidden). 无论哪种方式,我相信如果一个程序遇到异常,它应该始终通知链(即永远不会隐藏)。

create procedure mySimpleTableInsert
(
  @Id int
, @StringVar varchar(16) = null
, @IntVar int = null
)
as
begin
    --! Standard/ExceptionHandler variables
    declare @_FunctionName nvarchar(255) = quotename(object_schema_name(@@procid))
             + '.' + quotename(object_name(@@procid));
    declare @_Error int = 0;
    declare @_ReturnValue int;
    declare @_RowCount int = 0;
    declare @_Step varchar(128);
    declare @_Message nvarchar(1000);
    declare @_ErrorContext nvarchar(512);

    begin try
        set @_Step = 'Validate Inputs'
        if @Id is null raiserror('@Id is invalid: %i', 16, 1, @Id);

        set @_Step = 'Add Row'
        insert dbo.mySimpleTable (Id, StringVar, IntVar)
        values (@Id, @StringVar, @IntVar)
    end try
    begin catch
        set @_ErrorContext = 'Failed to add row to mySimpleTable at step: '
                 + coalesce('[' + @_Step + ']', 'NULL')

        exec log4.ExceptionHandler
                  @ErrorContext   = @_ErrorContext
                , @ErrorProcedure = @_FunctionName
                , @ErrorNumber    = @_Error out
                , @ReturnMessage  = @_Message out
        ;
    end catch

    --! Finally, throw any exception that will be detected by the caller
    if @_Error > 0 raiserror(@_Message, 16, 99);

    set nocount off;

    --! Return the value of @@ERROR (which will be zero on success)
    return (@_Error);
end
go

Let's start by creating a new schema (class) to hold our tests. 让我们首先创建一个新的模式(类)来保存我们的测试。

exec tSQLt.NewTestClass 'mySimpleTableInsertTests' ;
go

Our first test is the simplest and just checks that even if an exception is caught by our catch block, an error is still returned by the procedure. 我们的第一个测试是最简单和最简单的检查,即使我们的catch块捕获了异常,程序仍然会返回错误。 In this test we simply use exec tSQLt.ExpectException to check that an error is raised when @Id is supplied as NULL (which fails our input validation checks) 在这个测试中,我们只是使用exec tSQLt.ExpectException来检查当@Id作为NULL提供时出错(这导致我们的输入验证检查失败)

create procedure [mySimpleTableInsertTests].[test throws error from catch block]
as
begin
    exec tSQLt.ExpectException @ExpectedErrorNumber = 50000;

    --! Act
    exec dbo.mySimpleTableInsert @Id = null
end;
go

Our second test is a little more complex and makes use of tsqlt.SpyProcedure to "mock" the ExceptionHandler that would otherwise record the exception. 我们的第二个测试稍微复杂一点,并使用tsqlt.SpyProcedure “模拟”ExceptionHandler,否则会记录异常。 Under the hood, when we mock a procedure in this way, tSQLt creates a table named after the procedure being spied and replaces the spied procedure with one that just writes the input parameter values to that table. 在引擎盖下,当我们以这种方式模拟一个过程时,tSQLt创建一个以被监视的过程命名的表,并用一个只将输入参数值写入该表的过程替换该过程。 This is all rolled back at the end of the test. 这一切都在测试结束时回滚。 This allows us to can check that ExceptionHandler was called and what values were passed into it. 这允许我们可以检查是否已调用ExceptionHandler以及传递给它的值。 In this test we check that ExceptionHander was called by mySimpleTableInsert as a result of an input validation error. 在此测试中,我们检查由于输入验证错误,mySimpleTableInsert调用了ExceptionHander。

create procedure [mySimpleTableInsertTests].[test calls ExceptionHandler on error]
as
begin
    --! Set the Error returned by ExceptionHandler to zero so the sproc under test doesn't throw the error
    exec tsqlt.SpyProcedure 'log4.ExceptionHandler', 'set @ErrorNumber = 0;';

    select
          cast('Failed to add row to mySimpleTable at step: [Validate inputs]' as varchar(max)) as [ErrorContext]
        , '[dbo].[mySimpleTableInsert]' as [ErrorProcedure]
    into
        #expected

    --! Act
    exec dbo.mySimpleTableInsert @Id = null

    --! Assert
    select
          ErrorContext
        , ErrorProcedure
    into
        #actual
    from
        log4.ExceptionHandler_SpyProcedureLog;

    --! Assert
    exec tSQLt.AssertEqualsTable '#expected', '#actual';
end;
go

Finally the following (somewhat contrived) examples use the same pattern to check that an error is caught and thrown if the value of @IntVar is too big for the table: 最后,如果@IntVar的值对于表太大,下面(有点人为的)示例使用相同的模式来检查是否捕获并抛出了错误:

create procedure [mySimpleTableInsertTests].[test calls ExceptionHandler on invalid IntVar input]
as
begin
    --! Set the Error returned by ExceptionHandler to zero so the sproc under test doesn't throw the error
    exec tsqlt.SpyProcedure 'log4.ExceptionHandler', 'set @ErrorNumber = 0;';

    select
          cast('Failed to add row to mySimpleTable at step: [Add Row]' as varchar(max)) as [ErrorContext]
        , '[dbo].[mySimpleTableInsert]' as [ErrorProcedure]
    into
        #expected

    --! Act
    exec dbo.mySimpleTableInsert @Id = 1, @IntVar = 500

    --! Assert
    select
          ErrorContext
        , ErrorProcedure
    into
        #actual
    from
        log4.ExceptionHandler_SpyProcedureLog;

    --! Assert
    exec tSQLt.AssertEqualsTable '#expected', '#actual';
end;
go
create procedure [mySimpleTableInsertTests].[test throws error on invalid IntVar input]
as
begin
    exec tSQLt.ExpectException @ExpectedErrorNumber = 50000;

    --! Act
    exec dbo.mySimpleTableInsert @Id = 1, @IntVar = 500
end;
go

If this doesn't answer your question, perhaps you could post an example of what you are trying to achieve. 如果这不能回答你的问题,也许你可以发布一个你想要实现的例子。

You use RAISERROR in SQL Server to achieve this: 您在SQL Server中使用RAISERROR来实现此目的:

RAISERROR ('Error raised in TRY block.', -- Message text.
               16, -- Severity.
               1 -- State.
               );

You can see more information on the MSDN website: RAISERROR 您可以在MSDN网站上查看更多信息: RAISERROR

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM