Имитация методов библиотеки IFlurl с использованием NSubstitute вызывает исключение нулевой ссылки

Я использую flurl и пытаюсь протестировать следующий код:

public class MyRestClient
{
    public async Task<T> Request<T>(IFlurlRequest flurlRequest)
    {
        try  
        {
            return await flurlRequest
                    .WithOAuthBearerToken("my-hardcoded-token")
                    .GetAsync()
                    .ReceiveJson<T>();  
        }
        catch(HttpFlurlException)
        {
            throw new MyCustomException();
        }
    }
}

Что я хочу проверить, так это то, что если flurlRequest выдает исключение типа HttpFlurlException, то оно выдает MyCustomException. Моя идея состоит в том, чтобы moq flurlrequest и создать исключение. Вот как я выложил свой тест:

var moq = Substitute.For<IFlurlRequest>();
// Problem is with line below:
moq.When(x => x.WithOAuthBearerToken("dummy")).Do(x => { throw new HttpFlurlException(); } );

var myClient = new MyRestClient();

Func<Task> call = async () => { await myClient.Request<object>(moq); };

// FluentAssertions
call.Should().Throw<MyCustomException>();

Код при запуске возвращает исключение NullReferenceException:

Exception has occurred: CLR/System.NullReferenceException
An exception of type 'System.NullReferenceException' occurred in 
Flurl.Http.dll but was not handled in user code: 'Object reference not 
set to an instance of an object.'
at Flurl.Http.HeaderExtensions.WithHeader[T](T clientOrRequest, String name, Object value)

Итак, я вижу, что это связано с заголовками... поэтому я также попытался издеваться над этим, добавив:

var moq = Substitute.For<IFlurlRequest>();
moq.Headers.Returns(new Dictionary<string, object> { {"dummy", new {} };

Но я постоянно получаю одно и то же исключение. Что я делаю не так?


person boywonder    schedule 29.11.2018    source источник


Ответы (1)


WithOAuthBearerToken — это метод расширения, что означает, что NSubstitute не может напрямую издеваться над ним. Когда вы вызываете When..Do или Returns в методе расширения, он запускает реальный код метода расширения. (Я рекомендую добавить NSubstitute.Analyzers в ваш тестовый проект для обнаружения таких случаев.)

Прослеживая реализацию метода расширения на момент написания статьи, можно было бы смоделировать свойство Headers, чтобы вызвать требуемое исключение, но я думаю, что это требует слишком много внутренних знаний о библиотеке и приведет к хрупким тестам, которые тесно связаны с этой конкретной реализацией (это то, чего мы стремимся избежать с помощью насмешек!).

Я бы очень осторожно относился к издевательству над библиотекой третьей части таким образом, как я указал в этом ответе:

Другой вариант — проверить это на другом уровне. Я думаю, что проблема при тестировании текущего кода заключается в том, что мы пытаемся заменить детали [сторонней библиотеки], а не интерфейсы, которые мы создали для разделения логических деталей нашего приложения. Найдите «не имитируйте типы, которыми вы не владеете» для получения дополнительной информации о том, почему это может быть проблемой (я писал об этом ранее здесь).

Если возможно, я предлагаю попробовать использовать встроенную поддержку тестирования Flurl. Это должно позволить вам имитировать нужное вам поведение, не требуя конкретных подробностей о внутренней реализации Flurl.

person David Tchepak    schedule 29.11.2018
comment
Это блестяще, и я задавался вопросом — если у меня так много проблем с насмешкой над этим, не является ли это само по себе предупреждающим знаком, и так оно и было. Метод расширения имеет смысл, и я обязательно настрою анализатор NSub! Спасибо, вы очень помогли! - person boywonder; 30.11.2018