Программа установки Moq сообщает: «Выражение не является вызовом метода» - PullRequest
0 голосов
/ 04 апреля 2020

Я планировал написать тестовую базу для прокси / оболочек, которые у нас есть для нескольких сервисов. Каждый из прокси просто конвертирует модель в TIn, вызывает метод в сервисе, который возвращает TOut. Для каждого прокси-сервера единственным изменением является то, что базовый сервис отличается от того, что это просто стандартный код внутри каждого прокси-сервера.

class TestBase<TSvc, TProxy>
{
    private Mock<TSvc> svcMock = new Mock<TSvc>();
    private Mock<IMapper> mapperMock = new Mock<IMapper>();
    private TProxy proxy;

    protected void TestServiceCall<TIn, TOut>(Func<TSvc, Func<TIn, TOut>> svcCallFunc, TInModel model,
    Func<TProxy, TInModel, TOutModel> methodToTest)
    {
        var input= new TIn();
        var svcResult = new TOut();
        var proxy = ConstructProxy(this.svcMock);

        this.mapperMock.Setup(m => m.Map<TInModel, TInput>(model)).Returns(input);
        this.svcMock.Setup(m => svcCallFunc(m)(input)).Returns(svcResult);
        this.mapperMock.Setup(m => m.Map<TOut, TOutModel>(svcResult)).Returns(output);

        var result = methodToTest(model);

        ... verify if the svc was called etc. ...

        result.Should().BeSameAs(output);
    }
} 

class UserService
{
    QuestionModels GetQuestionsByUsers(SearchModel searchModel)
    { .. get all questions that comply with search parameters..}

    TopicModels GetTopicsForUsers(TopicSearchModel searchModel)
    { .. get all topics that comply with search parameters..}
}

class UserProxy
{
    private UserService service;
    private Mapper mapper;

    QuestionsClass GetQuestionsByUsers(SearchClass search)
    {
        var searchModel = mapper.Map<SearchClass, SearchModel>(search);

        var svcResult = userService.GetQuestionsByUsers(searchModel);

        return mapper.Map<QuestionModels, QuestionsClass>(svcResult);
    }

    TopicsClass GetTopicsForUsers(TopicSearchClass search)
    {
        var searchModel = mapper.Map<TopicSearchClass, TopicSearchModel>(search);

        var svcResult = userService.GetTopicsForUsers(searchModel);

        return mapper.Map<TopicModels, TopicsClass>(svcResult);
    }
}

Если вы видите, число шагов, которые каждый метод должен выполнить для получения результата, является постоянным,

  1. сопоставить запрос с промежуточным типом
  2. вызов службы для получения результата
  3. map & return result class

У меня есть десятки таких классов, которые делают примерно то же самое. Каждый из них может иметь около 5-10 таких методов.

Так что я буду sh делать следующее. Таким образом, я могу сэкономить время, затрачиваемое на написание стандартного тестового кода.

[TestClass]
class UserProxyTest : TestBase<UserService, UserProxy>
{
    [TestMethod]
    void GetQuestionsByUsersTest()
    {
        this.TestServiceCall<SearchModel, QuestionModels>(
        svc => svc.GetQuestionsByUsers,
        new SearchClass(), <-- This could be anything 
        pxy => pxy.GetQuestionsByUsers);
    }

    [TestMethod]
    void GetTopicsForUsers()
    {
        this.TestServiceCall<TopicSearchModel, TopicModels>(
        svc => svc.GetTopicsForUsers,
        new TopicSearchClass(), <-- This could be anything 
        pxy => pxy.GetTopicsForUsers);
    }   
}

Проблема, которую я получил, заключается в том, что строка, которая устанавливает svcMock, сообщает об ошибке, в которой говорится, что выражение не является методом призывание. Как мне исправить эту проблему?

Ответы [ 2 ]

1 голос
/ 05 апреля 2020

svcMock.Setup(m => svcCallFunc(m)(input)) недопустимо.

Метод Setup на Mock<T> принимает Expression, используемый для выбора члена T для имитации. Тело выражения должно быть либо выражением доступа к члену, либо выражением вызова метода для члена T. Другие произвольные выражения недопустимы.

Например:

public interface IService1
{
    int Test { get; }
    void Method1();
    object Method2(object input);
}

var mock = new Mock<IService1>();

// These are valid member selectors
mock.Setup(x => x.Test);
mock.Setup(x => x.Method1());
mock.Setup(x => x.Method2(null));

// These are not
mock.Setup(x => 1);
mock.Setup(x => x.Test.GetHashCode());
mock.Setup(x => this);

Чтобы сделать действительный вызов Setup, вам нужен либо экземпляр Expression<Func<TSvc, TOut>>, либо экземпляр Expression<Action<TSvc>> Таким образом, вы можете изменить свой TestServiceCall метод на:

protected void TestServiceCall<TIn, TOut>(
    Func<TIn, Expression<Func<TSvc, TOut>>> svcCallFunc)
{
    var input = new TIn();
    var output = new TOut();

    svcMock.Setup(svcCallFunc(input)).Returns(output);
}

и назвать его:

TestServiceCall<object, object>(input => svc => svc.Method2(input));
1 голос
/ 05 апреля 2020

В конечном счете, ошибка возникает потому, что аргумент .Setup() должен быть выражением, которое обращается к свойству устанавливаемого макета или вызывает один из его методов. Вы пытаетесь использовать выражение, которое не относится к этим двум вещам.

Вы не предоставили пример тестового метода в подклассе своего TestBase класса, но мне кажется разумным, что вы хотите передать параметр svcCallFunc как лямбду, ссылающуюся на один из ваших методов обслуживания, например, s => s.SomeServiceMethod. Если вы хотите сделать это, вам нужно объявить svcCallFunc как Expression<Func<...>>, а не Func<...>. Затем это выражение может быть передано непосредственно в this.svcMock.Setup(...).

Затем у вас есть два варианта выбора.

Во-первых, вы можете настроить способ вызова метода TestServiceCall. Вместо того, чтобы передавать сервисный метод для тестирования с использованием s => s.SomeServiceMethod, включите совпадение Moq It.IsAny() во входной аргумент, например, s => s.SomeServiceMethod(It.IsAny<TInputType>()). Ваш TestServiceCall затем настраивается на параметр Expression<Func<TSvc, TOut>> вместо Expression<Func<TSvc, Func<TIn, TOut>>>. В этом методе вы можете настроить макет, используя

this.svcMock.Setup(svcCallFunc).Returns(value => value == input ? output : null);

(Если TIn - это struct, вам может потребоваться изменить это.)

Во-вторых, вы можете оставить ваши вызовы TestServiceCall как есть, вместо этого выберите дерево выражений, созданное для s => s.SomeServiceMethod, и создайте новую функцию, представляющую вызов s.SomeServiceMethod с параметром input, используя следующий несколько сложный код:

        if (svcCallFunc.Body is UnaryExpression unExpr
            && unExpr.Operand is MethodCallExpression instMethCall
            && instMethCall.Object is ConstantExpression constant
            && instMethCall.Method.Name == "CreateDelegate"
            && instMethCall.Arguments.Count == 2
            && constant.Value is MethodInfo methodInfo)
        {
            // The expression given appears to be something like s => s.SomeServiceMethod,
            // so create s => s.SomeServiceMethod(input) from the parts of that.
            Expression<Func<TSvc, TOut>> setupLambda = Expression.Lambda<Func<TSvc, TOut>>(
                Expression.Call(instMethCall.Arguments[1], methodInfo, Expression.Constant(input)),
                svcCallFunc.Parameters);

            this.svcMock.Setup(setupLambda).Returns(output);
        }
        else
        {
            Assert.Fail("Unable to set up mock: lambda isn't as expected");
        }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...