测试是否在异步控制器操作中设置了线程区域性

问题描述 投票:2回答:1

我正在尝试将asp.net mvc 5控制器迁移为使用异步/等待。测试设置正确的文化时,我遇到了问题。

在旧的(非异步)代码版本中,单元测试通过,因为所有内容都在同一线程上运行。在新的代码(异步)版本中,单元测试失败,因为在调用async / await方法后未保留当前的区域性。

在新版本的代码中,正确设置了区域性,并且视图具有正确的区域性设置。唯一的问题是我无法在NUnit测试中对此进行测试,因为它在await调用之外。

您知道我该如何解决吗?我在下面提供了类似伪代码的代码。

旧代码:

public ActionResult Index()
{
    // Look up user in database
    var dbUser = database.GetUser(User.Identity.GetUserId());

    // Set preferred culture
    Thread.CurrentThread.CurrentCulture = new CultureInfo(dbUser.PreferredCulture);
    Thread.CurrentThread.CurrentUICulture = new CultureInfo(dbUser.PreferredCulture);

    // Do some other work
    DoSynchronousWork();

    return View();
}

[TestFixture]
public class TestClass
{
    [Test]
    public void TestIndex_CorrectCulture()
    {
        // Mock database
        database.GetUser().Returns(new User(){PreferredCulture = "de"});

        // Call controller
        _controller.Index();

        // Check that the thread culture was correctly set - this passes
        Assert.AreEqual("de", Thread.CurrentThread.CurrentCulture);
        Assert.AreEqual("de", Thread.CurrentThread.CurrentUICulture);
    }
}

新代码:

public async Task<ActionResult> Index()
{
    // Look up user in database
    var dbUser = database.GetUser(User.Identity.GetUserId());

    // Set preferred culture
    Thread.CurrentThread.CurrentCulture = new CultureInfo(dbUser.PreferredCulture);
    Thread.CurrentThread.CurrentUICulture = new CultureInfo(dbUser.PreferredCulture);

    // Do some other work
    await DoAsynchronousWork();

    return View();
}

[TestFixture]
public class TestClass
{
    [Test]
    public async Task TestIndex_CorrectCulture()
    {
        // Mock database
        database.GetUser().Returns(new User(){PreferredCulture = "de"});

        // Call controller
        await _controller.Index();

        // Check that the thread culture was correctly set - this fails because the culture inside _controller.Index() is lost after the await call
        Assert.AreEqual("de", Thread.CurrentThread.CurrentCulture);
        Assert.AreEqual("de", Thread.CurrentThread.CurrentUICulture);
    }
}
c# asp.net-mvc nunit cultureinfo
1个回答
0
投票

如果使用模拟库,请尝试将无法测试的代码移至服务:

interface IThreadCultureSetter
{
    void SetCurrentThreadCulture(CultureInfo ci, [CallerMemberName]string callerMethod = null);
}

class ThreadCultureSetter : IThreadCultureSetter
{
    public void SetCurrentThreadCulture(CultureInfo ci, [CallerMemberName]string callerMethod = null)
    {
        // Set thread culture
    }
}

更改控制器操作以使用界面:

private readonly IThreadCultureSetter _cultureSetter;

public async Task<ActionResult> Index()
{
    // Look up user in database
    var dbUser = database.GetUser(User.Identity.GetUserId());

    // Set preferred culture
    _cultureSetter.SetCurrentThreadCulture(new CultureInfo(dbUser.PreferredCulture));

    // Do some other work
    await DoAsynchronousWork();

    return View();
}

在您的测试中,提供一个模拟的IThreadCultureSetter并验证是否以所需的CultureInfo调用了该方法:

// Assume you use Moq
Mock<IThreadCultureSetter> cultureSetterMock;

// The SetCurrentThreadCulture must be called by Index method
mock.Verify(x => x.SetCurrentThreadCulture(It.Is<CultureInfo>(ci => ci.Name == "de"), "Index"));

事实是Thread.CurrentThread.CurrentCulture是异步静态的(值存储在AsyncLocal中),并且不是测试友好的,因为它的值仅存在于当前捕获的异步作用域中。

UPDATE

我刚刚发现有可能使用自定义SynchronizationContext测试线程的区域性是否设置为预期的区域性(无法保证:)

class ThreadCultureInspectionSynchronizationContext : SynchronizationContext
{
    private readonly string _expectedCultureName;

    public ThreadCultureInspectionSynchronizationContext(string expectedCultureName)
    {
        _expectedCultureName = expectedCultureName;
    }

    public bool WasSetToExpected { get; private set; }

    public override void Post(SendOrPostCallback d, object state)
    {
        // When context is switching, e.g. continuation of an await being executed, this method is called.
        // This method could inspect on thread culture, and set flag to true once expected culture detected.
        // This method can only prove that the thread culture was set, but cannot suggest by which method.

        if (!WasSetToExpected)
        {
            WasSetToExpected = Thread.CurrentThread.CurrentCulture.Name.Dump() == _expectedCultureName;
        }

        base.Post(d, state);
    }
}

在测试中,致电Index之前:

var ctx = new ThreadCultureInspectionSynchronizationContext("de");
SynchronizationContext.SetSynchronizationContext(ctx);
await Index();
...
Assert.IsTrue(ctx.WasSetToExpected);

如果您的线程文化是通过Index方法设置的,那么您期望看到的所有关于同步文化的线程文化检查都是您所期望的。

© www.soinside.com 2019 - 2024. All rights reserved.