遗留代码以某种方式对ThreadAbortException作出反应的单元测试

时间:2016-06-15 09:57:33

标签: c# .net unit-testing moq threadabortexception

我有一些遗留代码,我想要接受测试。这是必需品的责备:

public class LegacyUnit
{
    private readonly ICollaborator collaborator;

    public LegacyUnit(ICollaborator collaborator) 
    {
        this.collaborator = collaborator;
    }

    public object GetStuff(HttpContextBase context, string input)
    {
        try 
        {
            if (input == "")
            {
                context.Response.End();
            }

            collaborator.DoOtherStuff();

            return "Done!";
        }
        catch (ThreadAbortException) 
        { }

        return null;
    }
}

现在,这个遗留单元存在一些问题,但是现在我只是试图让它受到考验。具体来说,如果collaborator.DoOtherStuff提出Response.End(),我想测试ThreadAbort 被调用。

问题:你如何提出这样的例外?

我已阅读this question and its answers on ThreadAbortException,并了解它的特殊之处。但是,我从这些帖子中看不到如何在单元测试中处理这个问题。

这是我的尝试:

[Test]
public void DoesNotCallCollaboratorOnThreadAbort()
{
    var testResponseMock = new Mock<HttpResponseBase>();
    var testContextMock = new Mock<HttpContextBase>();
    var collaboratorMock = new Mock<ICollaborator>();

    testContextMock.Setup(x => x.Response).Returns(testResponseMock.Object);
    testResponseMock.Setup(x => x.End()).Throws<ThreadAbortException>(); // Compile error

    var unit = new LegacyUnit(collaboratorMock.Object);
    unit.GetStuff(testContextMock.Object, "");

    collaboratorMock.Verify(c => c.DoOtherStuff(), Times.Never);
}

显然编译器抱怨:ThreadAbortException没有可用的构造函数。此外,它sealed(可能是有充分理由的),所以创建了一个可测试的&#34;子类不会工作。

在测试中获取此类代码的正确方法是什么?它甚至是可行的,还是LegacyUnit对测试不友好?

完整,最小的repro(带有NUnit 2.6.4和Moq 4.5.9的空.NET 4.5类库):

public interface ICollaborator
{
    void DoOtherStuff();
}

public class LegacyUnit
{
    private readonly ICollaborator collaborator;

    public LegacyUnit(ICollaborator collaborator)
    {
        this.collaborator = collaborator;
    }

    public object GetStuff(HttpContextBase context, string input)
    {
        try
        {
            if (input == "") context.Response.End();
            collaborator.DoOtherStuff();
            return "Done!";
        }
        catch (ThreadAbortException)
        { }

        return null;
    }
}

[TestFixture]
public class LegacyUnitTests
{
    [Test]
    public void DoesNotCallCollaboratorOnThreadAbort()
    {
        var testResponseMock = new Mock<HttpResponseBase>();
        var testContextMock = new Mock<HttpContextBase>();
        var collaboratorMock = new Mock<ICollaborator>();

        testContextMock.Setup(x => x.Response).Returns(testResponseMock.Object);
        testResponseMock.Setup(x => x.End()).Throws<ThreadAbortException>(); // Compile error here

        var unit = new LegacyUnit(collaboratorMock.Object);
        unit.GetStuff(testContextMock.Object, "");

        collaboratorMock.Verify(c => c.DoOtherStuff(), Times.Never);
    }
}

1 个答案:

答案 0 :(得分:5)

通过调用ThreadAbortException在目标线程中引发

Abort。您可以创建一个线程来运行测试并在模拟Abort中调用testResponseMock.End,例如

testContextMock.Setup(x => x.Response).Returns(testResponseMock.Object);

var unit = new LegacyUnit(collaboratorMock.Object);
var thread = new Thread(() => unit.GetStuff(testContextMock.Object, ""));

testResponseMock.Setup(x => x.End()).Callback(() => { Thread.CurrentThread.Abort(); });

thread.Start();
thread.Join();

collaboratorMock.Verify(c => c.DoOtherStuff(), Times.Never);