Проверьте, что культура потока была установлена ​​в действии контроллера asyn c - PullRequest
2 голосов
/ 16 марта 2020

Я пытаюсь перенести контроллер asp. net mvc 5 для использования async / await. У меня проблема с проверкой правильности установленной культуры.

В старой (не асинхронной c) версии кода модульное тестирование проходит, поскольку все выполняется в одном потоке. В новой (asyn c) версии кода модульный тест не пройден, поскольку текущая культура не поддерживается после вызова метода 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);
    }
}

1 Ответ

0 голосов
/ 16 марта 2020

Если вы используете фиктивную библиотеку, попробуйте переместить код, не подлежащий тестированию, в службу:

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 является асинхронным c stati c (значение хранится в AsyncLocal) и не подходит для тестирования, поскольку его значение существует только для текущего захваченного асинхронного объема c.

ОБНОВЛЕНИЕ

Я только что понял, что можно использовать пользовательский 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, вы ожидаете увидеть все проверки на культуре потоков по контексту синхронизации - ваш ожидаемый.

...