I inherited an application with little-to-no unit testing and I'm trying to mock out a service class. The class takes in a viewModel as a string and a model as an object then returns the view as a string. It looks like the developer implemented something similar to 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();
}
}
}
}
The code works, but there aren't any tests for the service. I was able to mock everything with nSubstitute and it runs through with no exceptions, but I really don't understand what writer.ToString()
is supposed to be writing out or how to mock it.
I can see that await viewResult.View.RenderAsync(viewContext);
is rendering the viewContext, and that the ViewContext is taking in an instance of the StringWriter. However, I'm not able to step into the ViewEngineResult.View.RenderAsync method to see how the StringWriter works.
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));
}
}
}
Once everything was wired up I was expecting to see RENDERED
but all I get is an empty string.