Как смоделировать методы расширения с помощью Moq | Url.Page () - PullRequest
1 голос
/ 15 марта 2019

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

Класс PageModel имеет поле IUrlHelper, объявленное как Url.Интерфейс IUrlHelper имеет 5 методов, которые включают НЕ .Page ().Насмехаться над этими 5 было бы легко, но я понятия не имею, как имитировать методы расширения.

Может кто-нибудь помочь, пожалуйста?Я застрял на этом целую вечность.

RegisterModel

public class RegisterModel : PageModel
{
    private readonly IUrlHelper _urlHelper;

    public RegisterModel(
        IUrlHelper urlHelper)
        {}

    public async Task<IActionResult> OnPostAsync(
        string returnUrl = null)
    {
        returnUrl = returnUrl ?? Url.Content("~/");
        var callbackUrl = Url.Page(
                    "/Account/ConfirmEmail",
                    pageHandler: null,
                    values: new { userId = "full code has IdentityUserCreated", code = "string" },
                    protocol: Request.Scheme);
        LocalRedirect(returnUrl);                          
        return Page();
    }
}

RegisterModelTests

[TestFixture]
public class RegisterModelTests
{
    private Mock<IUrlHelper> _mockUrlHelper;

    [SetUp]
    public void SetUp()
    {
        _mockUrlHelper = new Mock<IUrlHelper>();
        SetUpUrlHelper();
    }

    public RegisterModel CreateRegisterModel()
    {
        return new RegisterModel(
            _mockUrlHelper.Object
        );
    }

    [Test]
    public async Task GivenValidInput_OnPostAsync_CreatesANewUser()
    {
        // Arrange
        var unitUnderTest = CreateRegisterModel();

        // Act
        var result = await unitUnderTest.OnPostAsync("/asdsad/asda");

        // Assert
        if (result != null)
            Assert.Pass();
    }

    private void SetUpUrlHelper()
    {
        _mockUrlHelper.Setup(x => x.Page(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<IdentityUser>(),
               It.IsAny<string>())).Returns("callbackUrl").Verifiable();
    }

Ответы [ 2 ]

1 голос
/ 15 марта 2019

Короткий ответ: вы не можете смоделировать метод расширения, поскольку он является статическим, а moq может обрабатывать только объект. Но этот пост расскажет вам больше.

Возможно, вы захотите изменить способ тестирования, переместив его на уровень контроллера. Но я думаю, что есть техническое решение. Мы можем сделать это, но, возможно, мы не должны.

Однако вы должны уметь подгонять метод. Это обмен адреса метода с другим. Убедитесь, что он вам действительно нужен, и убедитесь, что он используется только при тестировании.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;

public static class ShimHelper
{
    public static void Replace<TOriginal, TTarget>()
    {
        var typeOfOriginal = typeof(TOriginal);
        Replace<TTarget>(typeOfOriginal);
    }

    public static void Replace<TTarget>(Type typeOfOriginal)
    {
        var targetMethods = GetStaticPublicMethods<TTarget>();
        foreach (var targetMethod in targetMethods)
        {
            var parameters = targetMethod.GetParameters().Select(x => x.ParameterType).ToArray();
            var originalMethod = typeOfOriginal.GetMethod(targetMethod.Name, parameters);
            if (originalMethod != null)
            {
                SwapMethodBodies(originalMethod, targetMethod);
            }
            else
            {
                Debug.WriteLine(
                    "*****************************************************************************************");
                Debug.WriteLine($"Method not found - {targetMethod.Name}");
                Debug.WriteLine(
                    "*****************************************************************************************");
            }
        }
    }

    private static List<MethodInfo> GetStaticPublicMethods<T>()
    {
        return typeof(T).GetMethods(BindingFlags.Public | BindingFlags.Static)
            .Distinct().ToList();
    }

    private static void SwapMethodBodies(MethodInfo a, MethodInfo b)
    {
        RuntimeHelpers.PrepareMethod(a.MethodHandle);
        RuntimeHelpers.PrepareMethod(b.MethodHandle);

        unsafe
        {
            if (IntPtr.Size == 4)
            {
                Replace32Bit(a, b);
            }
            else
            {
                Replace64Bit(a, b);
            }
        }
    }

    private static unsafe void Replace64Bit(MethodInfo a, MethodInfo b)
    {
        var inj = (long*)b.MethodHandle.Value.ToPointer() + 1;
        var tar = (long*)a.MethodHandle.Value.ToPointer() + 1;
        *tar = *inj;
    }

    private static unsafe void Replace32Bit(MethodInfo a, MethodInfo b)
    {
        var inj = (int*)b.MethodHandle.Value.ToPointer() + 2;
        var tar = (int*)a.MethodHandle.Value.ToPointer() + 2;
        *tar = *inj;
    }
}

Использование:

ShimHelper.Replace<ExtensionClass, MockedExtensionClass>();

Где ваш смоделированный класс расширения точно соответствует сигнатуре метода. Запустите это в настройках вашего тестового прибора, и все будет хорошо.

0 голосов
/ 01 июня 2019

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

        public static void Replace(Type original, Type target)
        {
            var targetMethods = GetStaticPublicMethods(target);
            foreach (var targetMethod in targetMethods)
            {
                var parameters = targetMethod.GetParameters().Select(x => x.ParameterType).ToArray();
                var originalMethod = original.GetMethod(targetMethod.Name, parameters);
                if (originalMethod != null)
                {
                    SwapMethodBodies(originalMethod, targetMethod);
                }
                else
                {
                    Debug.WriteLine(
                        "*****************************************************************************************");
                    Debug.WriteLine($"Method not found - {targetMethod.Name}");
                    Debug.WriteLine(
                        "*****************************************************************************************");
                }
            }
        }

        private static List<MethodInfo> GetStaticPublicMethods(Type t)
        {
            return t.GetMethods(BindingFlags.Public | BindingFlags.Static)
                .Distinct().ToList();
        }

Использование сейчас:

ShimHelper.Replace(
                typeof(ExtensionClass), 
                typeof(MockedExtensionClass));

Я обнаружил, что это очень хорошо работает для расширений AjaxRequestExtensions в MVC.

...