Reputation: 65
I'm working on some legacy code (def: untested code - some well designed some not) and trying to develop some tests to confirm recent changes did what they expected etc. I'm running into an issue where I'm trying to force a method that has a try{catch} block in it to throw an exception using Moq. When I try to run the test it fails during the mock.Setup call with System.ArgumentException "Expression of type 'System.Web.Mvc.ActionResult' cannot be used for return type 'System.Web.Mvc.ActionResult'".
The basic setup of the code:
Interface for FilterController...
public interface IFilterController
{
ActionResult DeleteFilter(string reportFilter, bool customReport = true);
}
FilterController class...
public class FilterController : BaseController, IFilterController
{
public FilterController(
IServiceFactory serviceFactory,
IAwsServiceFactory awsServiceFactory,
IReportServiceFactory reportServiceFactory,
IAzureServiceFactory azureServiceFactory)
: base(typeof(FilterController), serviceFactory, awsServiceFactory, reportServiceFactory, azureServiceFactory)
{
}
// method under test
public ActionResult (string reportFilter, bool customReport = true) {
try {
// NOTE: I have trimmed down the actual code in the try block significantly for brevity - I should be able to hook onto something here as a way to mock something throwing an exception
var customReportFilterService = _serviceFactory.CreateCustomReportFilterService();
var emailReportSettingService = _serviceFactory.CreateEmailReportSettingService();
string message = string.Empty;
JsonReturnType type = JsonReturnType.DisplayMessage; // an enum
var filter = customReportFilterService.GetReportFilterByHash(SessionHelper.User.CustomerId, reportFilter, initLinkedProjects: true);
return JsonActionResult(type, ajaxMessage: message, redirectTo: filter == null ? null : string.Format("Report/{0}", filter.ReportName));
}
catch (Exception ex)
{
return JsonActionResult(JsonReturnType.Error, ajaxMessage: "There was an error in deleting the filter.");
}
}
}
BaseController class...
public class BaseController : Controller
{
private readonly ProgressController _progressController;
protected IServiceFactory _serviceFactory;
protected IAwsServiceFactory _awsServiceFactory;
protected IReportServiceFactory _reportServiceFactory;
protected IAzureServiceFactory _azureServiceFactory;
protected IApplicationSettingService _applicationSettingService;
protected IReportMonitorService _reportMonitorService;
protected ISymmetricAlgorithmProvider HiddenEncrypter { get; set; }
private Stopwatch _watch;
private bool _timePageEnabled;
private bool _maintenance;
private int _pageLoadThreshold;
private readonly ILog Logger;
public BaseController(Type type, IServiceFactory serviceFactory, IAwsServiceFactory awsServiceFactory, IReportServiceFactory reportServiceFactory, IAzureServiceFactory azureServiceFactory)
{
Logger = LogManager.GetLogger(type);
_progressController = new ProgressController();
_serviceFactory = serviceFactory;
_awsServiceFactory = awsServiceFactory;
_reportServiceFactory = reportServiceFactory;
_azureServiceFactory = azureServiceFactory;
_applicationSettingService = _serviceFactory.CreateApplicationSettingService();
_reportMonitorService = _serviceFactory.CreateReportMonitorService();
_watch = new Stopwatch();
_timePageEnabled = _applicationSettingService.ReadApplicationSettingFromCache<bool>(CC.Data.Model.Constants.ApplicationSettings.CheckSlowPageLoad, true);
_pageLoadThreshold = _applicationSettingService.ReadApplicationSettingFromCache<int>(CC.Data.Model.Constants.ApplicationSettings.PageLoadThreshold, 120);
_maintenance = _applicationSettingService.MaintenanceMode();
}
// System.Web.Mvc.ActionResult type mentioned in error
public ActionResult JsonActionResult(JsonReturnType returnType, string view = null, string ajaxMessage = null, string redirectTo = null, string target = null, object data = null, string popupTitle = null)
{
if (returnType == JsonReturnType.LoadContent)
_progressController.CompleteProgressCache();
var serializer = new JavaScriptSerializer();
serializer.MaxJsonLength = Int32.MaxValue;
var resultData = new {
ReturnType = returnType,
HtmlView = view,
Message = ajaxMessage,
RedirectTo = redirectTo,
Target = target,
CustomData = data,
ProjectId = SessionHelper.ProjectId,
PopupTitle = popupTitle,
MaintenanceMode = _maintenance
};
ContentResult result;
result = new ContentResult
{
Content = serializer.Serialize(resultData),
ContentType = "application/json"
};
return result;
}
}
Controller class...
public abstract class Controller : ControllerBase, IActionFilter, IAuthorizationFilter, IDisposable, IExceptionFilter, IResultFilter {
// stuff
}
Unit Test class...
[TestClass]
public class FilterControllerTest
{
private FilterController filterController;
private Mock<IFilterController> filterControllerMock;
private Mock<IServiceFactory> serviceFactoryMock;
private Mock<IAwsServiceFactory> awsServiceFactoryMock;
private Mock<IReportServiceFactory> reportServiceFactoryMock;
private Mock<IAzureServiceFactory> azureServiceFactoryMock;
private Mock<IApplicationSettingService> applicationSettingServiceMock;
[ClassInitialize]
public static void ClassInit(TestContext context)
{
}
[TestInitialize]
public void Initialize()
{
filterControllerMock = new Mock<IFilterController>();
serviceFactoryMock = new Mock<IServiceFactory>();
awsServiceFactoryMock = new Mock<IAwsServiceFactory>();
reportServiceFactoryMock = new Mock<IReportServiceFactory>();
azureServiceFactoryMock = new Mock<IAzureServiceFactory>();
applicationSettingServiceMock = new Mock<IApplicationSettingService>();
serviceFactoryMock
.Setup(s => s.CreateApplicationSettingService())
.Returns(applicationSettingServiceMock.Object);
filterController = new FilterController(
serviceFactoryMock.Object
, awsServiceFactoryMock.Object
, reportServiceFactoryMock.Object
, azureServiceFactoryMock.Object);
}
[TestCleanup]
public void Cleanup()
{
}
[ExpectedException(typeof(Exception))]
[TestMethod]
public void DeleteFilter_ExceptionThrown_IsCaughtAndLoggedAndReturnsActionResultOfError()
{
// Arrange
filterControllerMock
.Setup(x => x.DeleteFilter(It.IsAny<string>(), It.IsAny<bool>()))
.Throws(new Exception());
// Act
var result = filterController.DeleteFilter("myfilt", false);
}
}
In the end all I want to do is have it so when DeleteFilter is called for this test, an error is thrown and then I can assert what is returned from the catch block.
EDIT: have majorly updated the post by suggestion to make it easier to understand where the issue is.
Upvotes: 1
Views: 686
Reputation: 247323
Here is a slimmed down example
Given
public interface IServiceFactory {
object GetService(string args);
}
public class MyController : Controller {
IServiceFactory serviceFactory
public MyController(IServiceFactory serviceFactory) {
this.serviceFactory = serviceFactory;
}
// method under test
public ActionResult DeleteFilter(string args) {
try {
var model = serviceFactory.GetService(args);
return View(model);
} catch(Exception ex) {
return JsonActionResult(JsonReturnType.Error, ajaxMessage: "There was an error in deleting the filter.");
}
}
}
You can use moq in your test like this
[TestMethod]
public void DeleteFilter_ExceptionThrown_IsCaughtAndLoggedAndReturnsActionResultOfError() {
// Arrange
var serviceFactoryMock = new Mock<IServiceFactory>();
serviceFactoryMock
.Setup(x => x.GetService(It.IsAny<string>())
.Throws(new Exception())
.Verifiable();
var controller = new MyController(serviceFactoryMock.Object);
// Act
var result = controller.DeleteFilter("blah blah");
//Assert
serviceFactoryMock.Verify(); // verifies that the setup was invoked
Assert.IsNotNull(result);
Assert.IsInstanceOfType(result, typeof(JsonActionResult));
//...other assertions
}
So now in the example when DeleteFilter
is called, the mock service factory is invoked, an error is thrown based on the setup and you can assert what is returned from the catch block.
Upvotes: 1