测试在异步控制器操作中设置了线程文化
Test that thread culture was set in async controller action
我正在尝试迁移 asp.net mvc 5 控制器以使用 async/await。我在测试是否设置了正确的文化时遇到问题。
在代码的旧(非异步)版本中,单元测试通过了,因为一切都在同一个线程上 运行。在代码的新(异步)版本中,单元测试失败,因为在调用 async/await 方法后未维护当前区域性。
在新版本的代码中,文化设置正确,视图具有正确的文化集。唯一的问题是我无法在我的 NUnit 测试中对此进行测试,因为它在等待调用之外。
你知道我该如何解决这个问题吗?我提供了我在下面使用的代码的类似伪代码的版本。
旧代码:
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);
}
}
如果您使用的是模拟库,请尝试将不可测试的代码移至服务:
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 中)并且测试不友好,因为它的值仅存在于当前捕获的异步范围内。
更新
我刚刚发现可以使用自定义 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
方法设置的,您会希望看到同步上下文对线程文化的所有检查都是您期望的。
我正在尝试迁移 asp.net mvc 5 控制器以使用 async/await。我在测试是否设置了正确的文化时遇到问题。
在代码的旧(非异步)版本中,单元测试通过了,因为一切都在同一个线程上 运行。在代码的新(异步)版本中,单元测试失败,因为在调用 async/await 方法后未维护当前区域性。
在新版本的代码中,文化设置正确,视图具有正确的文化集。唯一的问题是我无法在我的 NUnit 测试中对此进行测试,因为它在等待调用之外。
你知道我该如何解决这个问题吗?我提供了我在下面使用的代码的类似伪代码的版本。
旧代码:
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);
}
}
如果您使用的是模拟库,请尝试将不可测试的代码移至服务:
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 中)并且测试不友好,因为它的值仅存在于当前捕获的异步范围内。
更新
我刚刚发现可以使用自定义 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
方法设置的,您会希望看到同步上下文对线程文化的所有检查都是您期望的。