Використання Moq для висміювання асинхронного методу для одиничного тесту


180

Я тестую метод для послуги, яка здійснює веб- APIдзвінок. Використання звичайного нормально HttpClientпрацює для одиничних тестів, якщо я також локально запускаю веб-сервіс (розміщений в іншому проекті в рішенні).

Однак, коли я перевіряю свої зміни, сервер збірки не матиме доступу до веб-сервісу, тому тести не зможуть.

Я створив спосіб цього для моїх тестових одиниць, створивши IHttpClientінтерфейс та застосувавши версію, яку я використовую у своїй програмі. Для одиничних тестів я складаю знущається версію в комплекті з методом глузливого асинхронного повідомлення. Ось де у мене виникли проблеми. Я хочу повернути ОК HttpStatusResultдля цього конкретного тесту. Для чергового подібного тесту я поверну поганий результат.

Тест запуститься, але ніколи не завершиться. Він висить у очікуванні. Я новачок в асинхронному програмуванні, делегатах і самому Moq, і я шукав SO і Google деякий час, вивчаючи нові речі, але я все ще не можу подолати цю проблему.

Ось метод, який я намагаюся перевірити:

public async Task<bool> QueueNotificationAsync(IHttpClient client, Email email)
{
    // do stuff
    try
    {
        // The test hangs here, never returning
        HttpResponseMessage response = await client.PostAsync(uri, content);

        // more logic here
    }
    // more stuff
}

Ось мій метод одиничного тестування:

[TestMethod]
public async Task QueueNotificationAsync_Completes_With_ValidEmail()
{
    Email email = new Email()
    {
        FromAddress = "bob@example.com",
        ToAddress = "bill@example.com",
        CCAddress = "brian@example.com",
        BCCAddress = "ben@example.com",
        Subject = "Hello",
        Body = "Hello World."
    };
    var mockClient = new Mock<IHttpClient>();
    mockClient.Setup(c => c.PostAsync(
        It.IsAny<Uri>(),
        It.IsAny<HttpContent>()
        )).Returns(() => new Task<HttpResponseMessage>(() => new HttpResponseMessage(System.Net.HttpStatusCode.OK)));

    bool result = await _notificationRequestService.QueueNotificationAsync(mockClient.Object, email);

    Assert.IsTrue(result, "Queue failed.");
}

Що я роблю неправильно?

Дякую за твою допомогу.

Відповіді:


351

Ви створюєте завдання, але ніколи не починаєте його, тому воно ніколи не завершується. Однак не просто запускайте завдання - замість цього перейдіть до використання, Task.FromResult<TResult>яке дасть вам вже виконане завдання:

...
.Returns(Task.FromResult(new HttpResponseMessage(System.Net.HttpStatusCode.OK)));

Зауважте, що ви не будете тестувати фактичну асинхронію таким чином - якщо ви хочете це зробити, вам потрібно зробити трохи більше роботи, щоб створити Task<T>те, що ви зможете контролювати більш дрібнозернистим способом ... але це щось для інший день.

Можливо, ви також захочете скористатися підробкою, IHttpClientа не знущатися над усім - це насправді залежить від того, як часто вам це потрібно.


2
Велике спасибі. Це працювало чудово. Я подумав, що це, мабуть, щось просте, чого я не розумію.
mvanella

2
Re: Підроблений IHttpClient, я вважав це, але мені потрібно було вміти повертати різні HttpStatusCodes для різних тестів на основі очікуваної поведінки, що повертається з веб-API, і це, здавалося, дає мені більше контролю.
mvanella

3
@mvanella: Так, ви створили б підробку, яка може повернути все, що ви хочете. Просто над чим подумати.
Джон Скіт

134
Для тих, хто зараз виявить це, Moq 4.2 має розширення ReturnsAysnc, яке називається , що робить саме це.
Стюарт Грассі

3
@legacybass Я не можу знайти посилання на будь-яку документацію для цього, навіть якщо документи API говорять, що вони створені проти версії v4.2.1312.1622, яка вийшла майже річно тому. Дивіться цю прихильність, яку було зроблено за кілька днів до цього випуску. Щодо того, чому документи API не оновлюються ...
Stuart Grassie

17

Рекомендуйте відповідь @Stuart Grassie вище.

var moqCredentialMananger = new Mock<ICredentialManager>();
moqCredentialMananger
                    .Setup(x => x.GetCredentialsAsync(It.IsAny<string>()))
                    .ReturnsAsync(new Credentials() { .. .. .. });

1

З методом Mock.Of<...>(...)для asyncви можете використовувати Task.FromResult(...):

var client = Mock.Of<IHttpClient>(c => 
    c.PostAsync(It.IsAny<Uri>(), It.IsAny<HttpContent>()) == Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK))
);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.