Насмешливая StreamWriter ViewContext для вывода результата ViewEngineResults.View.RenderAsync? - PullRequest
1 голос
/ 29 мая 2019

Я унаследовал приложение с модульным тестированием практически без тестов, и я пытаюсь смоделировать класс обслуживания.Класс принимает viewModel как строку, а модель как объект, а затем возвращает представление как строку.Похоже, разработчик реализовал нечто похожее на this .

using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using FluidMvcViewEngine;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;

namespace Some.Api.Services
{
    public class ViewRenderService
    {
        private readonly IActionContextAccessor _context;
        private readonly IFluidViewEngine _fluidViewEngine;
        private readonly ITempDataProvider _tempDataProvider;

        public ViewRenderService(IActionContextAccessor context, IFluidViewEngine razorViewEngine, ITempDataProvider tempDataProvider)
        {
            _context = context;
            _fluidViewEngine = razorViewEngine;
            _tempDataProvider = tempDataProvider;
        }

        public async Task<string> RenderToStringAsync(string viewName, object model, CancellationToken ct = default(CancellationToken))
        {
            var actionContext = _context.ActionContext;

            using (var writer = new StringWriter())
            {
                var viewResult = _fluidViewEngine.GetView(viewName, viewName, false);

                if (viewResult.View == null)
                {
                    throw new ArgumentNullException("exception message goes here");
                }

                var viewDictionary = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary())
                {
                    Model = model
                };

                var viewContext = new ViewContext(
                    actionContext,
                    viewResult.View,
                    viewDictionary,
                    new TempDataDictionary(actionContext.HttpContext, _tempDataProvider),
                    writer,
                    new HtmlHelperOptions()
                );

                await viewResult.View.RenderAsync(viewContext);
                return writer.ToString();
            }
        }
    }
}

Код работает, но тестов для сервиса нет.Я смог все смоделировать с помощью nSubstitute, и он работает без исключений, но я действительно не понимаю, что writer.ToString() должен писать или как его высмеивать.

Я вижу, что await viewResult.View.RenderAsync(viewContext); отображает viewContext, и что ViewContext получает экземпляр StringWriter.Однако я не могу войти в метод ViewEngineResult.View.RenderAsync, чтобы увидеть, как работает StringWriter.

using System.Threading.Tasks;
using FluentAssertions;
using FluidMvcViewEngine;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Routing;
using NSubstitute;
using Xunit;

namespace Some.Tests.UnitTests.Services
{
    public class ViewRenderServiceTest
    {
        private readonly IActionContextAccessor _context;
        private readonly IFluidViewEngine _fluidViewEngine;
        private readonly ViewRenderService _service;
        private readonly ITempDataProvider _tempDataProvider;
        private readonly IView _view;
        private readonly IViewEngine _viewEngine;

        public ViewRenderServiceTest()
        {
            _context = Substitute.For<IActionContextAccessor>();
            _fluidViewEngine = Substitute.For<IFluidViewEngine>();   
            _tempDataProvider = Substitute.For<ITempDataProvider>(); 
            _view = Substitute.For<IView>();
            _viewEngine = Substitute.For<IViewEngine>();   

            _service = new ViewRenderService(_context, _fluidViewEngine, _tempDataProvider);         
        }

        [Fact]
        public async Task RenderToStringAsyncTest()
        {
            // Arrange
            var stringToRender = "RENDERED";
            var someObject = new { Id = default(int), Name = "Bob" };

            // Mock IActionContextAccessor 
            MockIActionContextAccessor();

            // Mock IFluidViewEngine
            MockIFluidViewEngine(stringToRender);

            // Act
            var test = await _service.RenderToStringAsync(stringToRender, someObject);

            // Assert
            test.Should().Be(stringToRender);
        }

        private void MockIActionContextAccessor()
        {
            var httpContext = Substitute.For<HttpContext>();
            var routeData = Substitute.For<RouteData>();
            var actionDescriptor = Substitute.For<ActionDescriptor>();
            var modelState = Substitute.For<ModelStateDictionary>();

            _context.ActionContext.Returns(new ActionContext(httpContext, routeData, actionDescriptor, modelState));
        }

        private void MockIFluidViewEngine(string stringToRender)
        {
            _view
                .RenderAsync(Arg.Any<ViewContext>())
                .Returns(Task.FromResult(stringToRender));

            _view
                .Path
                .Returns(string.Empty);

            _viewEngine
                .GetView(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<bool>())
                .Returns(ViewEngineResult.Found(stringToRender, _view));


            _fluidViewEngine
                .GetView(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<bool>())
                .Returns(ViewEngineResult.Found(stringToRender, _view));
        }
    }
}

Как только все было подключено, я ожидал увидеть RENDERED, но все, что я получаюпустая строка.

1 Ответ

1 голос
/ 30 мая 2019

Я предполагаю, что _view.RenderAsync(...) нужно будет высмеять, чтобы написать ViewContext.Writer.

Вы можете попробовать что-то вроде:

_view
    .RenderAsync(Arg.Any<ViewContext>())
    .Returns(x => x.Arg<ViewContext>().Writer.WriteAsync(stringToRender));

Или:

_view
    .RenderAsync(Arg.Do<ViewContext>(vc => vc.Writer.Write(stringToRender));

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

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

...