【问题标题】:How do I unit test a class that relies on HttpContext.GetGlobalResourceObject?如何对依赖 HttpContext.GetGlobalResourceObject 的类进行单元测试?
【发布时间】:2018-06-06 15:48:52
【问题描述】:

我正在尝试向网络表单项目添加测试。有一种静态方法可以从资源文件中获取行。我正在尝试测试的其中一个类依赖于从资源文件中获取文本。

public static class MyStaticClass {
    public static string getText(String name)
    {
        String s = HttpContext.GetGlobalResourceObject("MyResources", name).ToString();
        return s;
    }
}

public class ClassUnderTest
{
    // returns: "Hey it's my text"
    private string _eg = MyStaticClass.getText("label_in_resources.resx_file")
}

class UnitTests
{
    [Test]
    public void TestMyClass()
    {
        ClassUnderTest _cut = new ClassUnderTest();
        // errors out because ClassUnderTest utilizes getText
        // which requires HttpContext.GetGlobalResourceObject
        // ... other stuff
    }
}

注意:这些都是简单的例子。

问题是我收到一条测试失败的消息:

消息:System.NullReferenceException:对象引用未设置为对象的实例。

根据我的调查,我确定这是因为HttpContext 在这些测试中为空。

我看过很多关于模拟 HttpContext 的 SO 帖子,但我认为我并不完全理解他们到底在做什么,因为他们通常处理的是 MVC 而不是 Webforms。他们中的大多数仍然使用HttpContextBase 和/或HttpContextWrapper 但同样,我不知道如何实现它们。

另外 - 我没有直接测试 getText 方法。我知道它有效。我正在测试一个使用它的类。在这种情况下,嘲笑HttpContext 会有所帮助吗?

我确实意识到这是一种单元测试/集成测试的混合体,所以如果这不是最好的方法,我会全神贯注……或者……更确切地说……眼睛。

编辑

现在,如果HttpContext.GetGlobalResourceObject 的结果为空,我修改了我的getText 方法以返回键(名称)。然后我更新了我的测试以期望键而不是值。这并不理想,但它有效并允许我继续。如果有更好的方法,请告诉我。

public static class MyStaticClass {
    public static string getText(String name)
    {
        String s = HttpContext.GetGlobalResourceObject("MyResources", name);
        return s != null ? s.ToString() : name;
    }
}

【问题讨论】:

  • 最好的选择是用具有接口的包装器完全替换 GetGlobalResourceObject。但是,如果你不能,我发现 Microsoft Fakes 是我在测试时不得不模拟静态数据的首选。
  • @TyCobb 你能举个例子吗?我相信我已经包装了GetGlobalResourceObject,除非我误解了。另外,如果不清楚,我不会直接测试静态方法本身,但这就是错误的根源。
  • 我正在做一个。我错过了getText 是静态的。您可以伪造该课程。现在正在处理一个示例。

标签: c# unit-testing webforms nunit-3.0


【解决方案1】:

Fakes 的原始答案(关于消除静电的处理见下文)

所以在我尝试这样做之前,我完全忘记了一个警告。我很确定 Fakes 仍然需要企业版的 VS。我不知道是否有办法让它与 NUnit 一起工作,但是当你无法更改代码时,有时你必须处理它。

这是一个 Shimming 你的静态方法的例子。你不需要担心 HttpContext (还),因为你没有直接使用它。相反,您可以 Shim 您的 getText(string) 方法。

实际商业项目

namespace FakesExample
{
    public class MyStaticClass
    {
        public static string GetText(string name)
        {
            throw new NullReferenceException();
        }
    }
}

你的单元测试项目

using System;
using Microsoft.QualityTools.Testing.Fakes;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace FakesExampleTests
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void TestMethod1()
        {
            using (ShimsContext.Create())
            {
                FakesExample.Fakes.ShimMyStaticClass.GetTextString = (s) =>
                {
                    return "Go away null reference";
                };

                Console.WriteLine(FakesExample.MyStaticClass.GetText("foo"));
            }
        }
    }
}

我实际上运行了这个,所以我知道它有效。发生的情况是,即使 GetText 在调用时总是会抛出 NullReferenceException,但我们的 Shim 会返回我们自己的自定义消息。

您可能需要创建一个 Visual Studio 测试项目。

在您的单元测试项目中,右键单击您的参考并说“添加假货”。它将为您的程序集生成所有垫片和存根。


去静电的过程

最好的解决方案是实际消除静电。您已经找到了不使用它们的一个主要原因。

以下是我将如何删除静态并删除对 HttpContext 的依赖

public interface IResourceRepository
{
    string Get(string name);
}

public class HttpContextResourceRepository : IResourceRepository
{
    public string Get(string name)
    {
        return HttpContext.GetGlobalResourceObject("MyResources", name).ToString();
    }
}

public class MyFormerStaticClass
{
    IResourceRepository _resourceRepository;

    public MyFormerStaticClass(IResourceRepository resourceRepository)
    {
        _resourceRepository = resourceRepository;
    }

    public string GetText(string name)
    {
        return _resourceRepository.Get(name);
    }
}

然后,我将利用依赖注入来处理在实际业务代码中创建HttpContextResourceRepositoryMyStaticClass(可能也应该被接口)。

对于单元测试,我会模拟实现

[TestFixture]
public class UnitTest1
{
    [Test]
    public void TestMethod1()
    {
        var repoMock = new Mock<IResourceRepository>();
        repoMock.Setup(repository => repository.Get("foo")).Returns("My Resource Value");

        var formerStatic = new MyFormerStaticClass(repoMock.Object);
        Console.WriteLine(formerStatic.GetText("foo"));
    }
}

走这条路,您可以创建任意数量的 IResourceRepository 实现并随时交换它们。

【讨论】:

  • 很遗憾,我没有 Enterprise。无关:我喜欢你的头像。 :)
  • 我并不是要苛刻,但是你需要彻底检查你的东西并停止使用静态。这些东西需要被抽象出来。编辑:谢谢xD
  • 我认为静电不是问题。问题是没有HttpContext 可以从.resx 文件中获取值。我想我最终要做的是修改我的 getText 方法,以便如果上下文不存在,它只会返回字符串标识符。至少这样我就可以继续前进,我可以修改我的测试以期望键而不是值。我不确定我是否可以将其抽象出来。如果可以的话,我不完全确定该怎么做。
  • 不,静态正是问题所在——你不能模拟你自己的类!您修复它的建议现在将改变您系统的输出。查看 SOLID。
  • 哈!我只是回到这里说我已经重构了我的类以拥有一个接口并且我正在模拟接口。到目前为止,一切都很好。我想我现在走在正确的轨道上。谢谢:D
猜你喜欢
  • 1970-01-01
  • 2014-05-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多