Controller test fails only on server - c#

So I built unit tests for an ApiController, and one of those fails only on the server in the CI pipeline with the exception System.InvalidOperationException : HttpControllerContext.Configuration must not be null.
The structure looks like this:
public IHttpActionResult MyControllerMethod()
{
if(this.SomeThings()) // uses this.IService and this.IService2
return this.UnAuthorized();
DoOtherStuff();
return this.Ok();
}
In my test, I mock IService and IService2 and pass them to the controller:
var controllerUnderTest = new MyController(serviceMock, serviceMock2);
Then I call the method with some invalid data, provoking the return of UnAuthorized():
var result = await controllerUnderTest.MyControllerMethod(invalidData).ExecuteAsync(CancellationToken.None);`
At first, this failed. So I added
controllerUnderTest.Configuration = new HttpConfiguration();
controllerUnderTest.Request = new HttpRequestMessage();
from this Stackoverflow post.
So now it works. Locally. It works when debugging, it works using ReSharper's testrunner, it works using NCrunch.
But it doesn't work in the pipeline.
When executed in the pipeline using ncrunch console tool, the tests fails with above message.
Any idea how to fix this? How can config be nul when I explicitely initialize it?
Edit: Here is the stacktrace:
System.InvalidOperationException : HttpControllerContext.Configuration must not be null.
bei System.Web.Http.Results.ExceptionResult.ApiControllerDependencyProvider.EnsureResolved()
bei System.Web.Http.Results.ExceptionResult.ApiControllerDependencyProvider.get_IncludeErrorDetail()
bei System.Web.Http.Results.ExceptionResult.Execute()
bei System.Web.Http.Results.ExceptionResult.ExecuteAsync(CancellationToken cancellationToken)

Related

Xunit & MOq - Unable to setup expectation for IndexDocumentsAsync

I have a method that inserts documents into Azure cognitive search but I am struggling to unit-test the method. My method looks like below:
public async Task AddDocumentIntoIndexAsync(Document[] Documents, string IndexName)
{
IndexDocumentsBatch<CreClaim> batch = IndexDocumentsBatch.MergeOrUpload(Documents);
try
{
IndexDocumentsResult result = await _searchClient.IndexDocumentsAsync(batch);
}
catch (Exception ex)
{
}
}
}
I am using dependency injection to inject _searchClient which is an instance of type Azure.Search.Documents.SearchClient.
In my unit test project, I am mocking my SearchClient as below:
_searchClient = new Mock<SearchClient>(new Uri(endpoint),indexName, new AzureKeyCredential(key));
The problem happens when I try to setup IndexDocumentsAsync.
var indexresult = SearchModelFactory.IndexDocumentsResult;
_searchClient.Setup(x => x.IndexDocumentsAsync(batch, options, new CancellationToken())).Returns(indexresult);
When I run this code, I get a runt time error:
: 'Invalid callback. Setup on method with 3 parameter(s) cannot invoke callback with different number of parameters (1).'
I thought may be its because the last 2 params of the method IndexDocumentsAsync are optional so I tried with
_searchClient.Setup(x => x.IndexDocumentsAsync(batch)).Returns(indexresult);
With this I get a compilation issue:
Can anyone help please?

How to check that an error is logged in a unit test for ASP.NET Core 3.0?

I want to create a unit test to ensure that a method is logging an error using xUnit and Moq. This code worked in ASP.NET Core 2.1:
//Arrange
var logger = new Mock<ILogger<MyMiddleware>>();
var httpContext = new DefaultHttpContext();
var middleware = new MyMiddleware(request => Task.FromResult(httpContext), logger.Object);
//Act
await middleware.InvokeAsync(httpContext);
//Assert
logger.Verify(x => x.Log(LogLevel.Error, It.IsAny<EventId>(), It.IsAny<FormattedLogValues>(), It.IsAny<Exception>(), It.IsAny<Func<object, Exception, string>>()), Times.Once);
To validate that _logger.LogError("Error message"); was called in middleware.InvokeAsync.
However, in ASP.NET Core 3.0, I am unable to verify that the logger is being called. Microsoft.Extensions.Logging.Internal can no longer be referenced, so FormattedLogValues is unavailable.
I tried changing the Assert() to use object instead of FormattedLogValues, and also IReadOnlyList<KeyValuePair<string, object>> since that is what FormattedLogValues is based on (FormattedLogValues.cs).
This is the error message I am getting in the Visual Studio test runner:
Message:
Moq.MockException :
Expected invocation on the mock once, but was 0 times: x => x.Log<object>(LogLevel.Error, It.IsAny<EventId>(), It.IsAny<object>(), It.IsAny<Exception>(), It.IsAny<Func<object, Exception, string>>())
Performed invocations:
ILogger.Log<FormattedLogValues>(LogLevel.Error, 0, Error message, null, Func<FormattedLogValues, Exception, string>)
Stack Trace:
Mock.Verify(Mock mock, LambdaExpression expression, Times times, String failMessage)
Mock`1.Verify(Expression`1 expression, Times times)
Mock`1.Verify(Expression`1 expression, Func`1 times)
MyMiddlewareTests.InvokeAsync_ErrorIsLogged() line 35
--- End of stack trace from previous location where exception was thrown ---
How can I validate the error is being logged in ASP.NET Core 3.0?
There is an issue on the aspnet github page about this. It seems the problem is with Moq and they have made changes to fix it.
You will need to upgrade to Moq 4.13 to get the fix.
They have introduced an It.IsAnyType to resolve the problem with internal objects so you should be able to change the object reference to It.IsAnyType and write your test like this:
logger.Verify(x => x.Log(LogLevel.Error, It.IsAny<EventId>(), It.IsAny<It.IsAnyType>(), It.IsAny<Exception>(), (Func<It.IsAnyType, Exception, string>)It.IsAny<object>()), Times.Once);
Note: The last parameter needs to be type cast as Moq doesn't currently support nested type matchers.
More details from Moq can be found here
This is a bit late, but I just want to add that if you are asserting for specific messages being logged, you can create a custom matcher that inspects the message:
logger.Verify(x => x.Log(LogLevel.Error,
It.IsAny<EventId>(),
It.Is<It.IsAnyType>((x, _) => LogMessageMatcher(x, "Expected error message")),
It.IsAny<Exception>(),
It.IsAny<Func<It.IsAnyType, Exception, string>>()), Times.Once);
The MatcherMethod would be a static method like so:
public static bool LogMessageMatcher(object formattedLogValueObject, string message)
{
var logValues = formattedLogValueObject as IReadOnlyList<KeyValuePair<string, object>>;
return logValues.FirstOrDefault(logValue => logValue.Key == "{OriginalFormat}")
.Value.ToString() == message;
}
I use SGA (Setup, Grab, Assert) approach.
//In the setup part
var mockLog = new Mock<ILogger>(MockBehavior.Strict);
string error = null;
mockLog.Setup(l => l
.Error(It.IsAny<Exception>(), It.IsAny<string>()))
.Callback((Exception b, string c) =>
{
error = c + " " + b.GetBaseException().Message;
// This would keep only the last error, but it's OK, since there should be zero
// Sometimes I use a collection and append the error info.
});
//In the assert part
Assert.IsNull(error, $"Error detected: {error}");

MVC unit testing a RedirectToAction that returns with a status code

I am trying to write a unit test for a controller that returns a RedirectToAction with the view, the controller, and also a status code. The return is below
return RedirectToAction("Index", "Errors", new {statusCode = StatusCodes.Status505HttpVersionNotsupported});
I have found several stack overflow articles but none of them helped me figure out what the problem is my unit tests look like.
[TestMethod]
public void Index_OnError_ThrowsException()
{
//Arrange
Service.Setup(m => m.GetAllViewModels()).Throws(new NullReferenceException());
//Act
var result = (RedirectToRouteResult) controller.Index();
//Assert
Assert.AreEqual("Index", result.RouteValues["action"]);
Assert.AreEqual("Error", result.RouteValues["controller"]);
}
I keep get an exception thrown that says it can't cast
RedirectToRouteResult to type RedirectToRouteResult
And I can't figure out another way to run this unit test so I can test my controller.
I found the answer after a few hours of frustration trying to figure out why the answers I found off stack overflow weren't working.
The problem was with all the answers I found was they wanted you to cast the RedirectToAction to RedirectToRouteResult if you do this you will run into an exception that gets thrown because it can't cast the RedirectToAction that we are using to RedirectToRouteResult the exception you get is
System.InvalidCastException: 'Unable to cast object of type 'Microsoft.AspNetCore.Mvc.RedirectToActionResult' to type 'Microsoft.AspNetCore.Mvc.RedirectToRouteResult'.'
To fix this issue so you can unit test the controller, all you need to do is cast your controller call to RedirectToActionResult and then do the asserts so they look like Assert.AreEqual("Index", result.RouteValues["valueName"]);. The full code block looks like.
[TestMethod]
public void Index_OnError_RedirectsToErrorPage()
{
//Arrange
Service.Setup(m => m.GetAllViewModels()).Throws(new NullReferenceException());
//Act
var result = (RedirectToActionResult)controller.Index();
//Assert
Assert.AreEqual("Index", result.ActionName);
Assert.AreEqual("Errors", result.ControllerName);
}

AddCombresRoute throwing ArgumentNullException

I've created a new test project in a pre-existing Visual Studio solution and attempting to mock a controller so I can test routing. The main project makes use of Combres for minification of css etc. To better demonstrate the problem I've put the .AddCombresRoute into the test which generates the error I'm trying to solve.
private HttpContextBase rmContext;
private HttpRequestBase rmRequest;
[TestInitialize]
public void SetupTests()
{
// Setup Rhino Mocks
rmContext = MockRepository.GenerateMock<HttpContextBase>();
rmRequest = MockRepository.GenerateMock<HttpRequestBase>();
rmContext.Stub(x => x.Request).Return(rmRequest);
}
[TestMethod]
public void RhinoMocksRoutingTest()
{
// Arrange
RouteCollection routes = new RouteCollection();
RouteConfig.RegisterRoutes(routes);
rmRequest.Stub(e => e.AppRelativeCurrentExecutionFilePath).Return("~/Home/Index");
// Act
routes.AddCombresRoute("Combres Route"); *** ERRROR HERE ***
RouteData routeData = routes.GetRouteData(rmContext);
// Assert
Assert.IsNotNull(routeData);
Assert.AreEqual("Home",routeData.Values["controller"]);
Assert.AreEqual("Index",routeData.Values["action"]);
}
Despite making the correct references, ensuring combres.xml and combres.xsd are in App_Data (and copied to local) and dropping in the relevant entries into app.config I get the following error when I run the test:
ArgumentNullException was unhandled by user code. An exception of type
'System.ArgumentNullException' occurred in System.Xml.dll was not
handled in user code. Additional information: Value cannot be null.

NullReferenceException when calling Controller.Execute

I have run into some code that calls Controller.Execute, via a derived controller class. The derived class, ErrorController doesn't override Execute though, and the RequestContext parameter passed in is not null, although several of its properties are. How can I determine which part of RequestContext is the problem that makes 'Execute' throw NullReferenceException?
Here is the code that calls Execute:
public class AuthenticationManager : ClaimsAuthenticationManager
{
public override ClaimsPrincipal Authenticate(string resourceName, ClaimsPrincipal incomingPrincipal)
{
try
{
throw new Exception();
if (incomingPrincipal != null && incomingPrincipal.Identity.IsAuthenticated)
{
signInClient.TransformClaimsBasedOnUserRole(incomingPrincipal.Identity.AsClaimsBasedIdentitiy());
}
return base.Authenticate(resourceName, incomingPrincipal);
}
catch (Exception e)
{
var context = HttpContext.Current;
var routeData = new RouteData();
routeData.Values.Add("controller", "Error");
routeData.Values.Add("action", "Index");
routeData.Values.Add("errorId", logId);
routeData.Values.Add("exceptionMessage", "");
IController controller = new ErrorController();
var ctx = new RequestContext(new HttpContextWrapper(context), routeData);
controller.Execute(ctx);
}
}
}
I had to slip in the throw to reproduce the Execute exception. The other auth code only throws on very rare occasions.
As requested:
public class ErrorController: Controller
{
public ActionResult Index(Guid? errorId, string exceptionMessage)
{
ErrorModel resultModel;
try
{
resultModel = new ErrorModel
{
ErrorId = errorId==null ? Guid.NewGuid() : Guid.Parse(errorId.ToString()) ,
ErrorMessage = (string.IsNullOrEmpty(exceptionMessage)) ? ConfigurationManager.AppSettings["GenericError"] : exceptionMessage,
};
if (User.IsInRole(RoleIdentifiers.InActive))
{
Authentication.AuthenticationManager.SignOut();
}
}
catch (Exception e)
{
LogProvider.Current.LogError(LogLevel.Fatal, e, "Error constructing error result model for error Id [{0}]", errorId);
return new HttpNotFoundResult();
}
return View(resultModel);
}
public ActionResult SessionTimeOut(string rtnController = "Home", string rtnAction="Index")
{
return View(new SessionTimeOutViewModel { RedirectAction = rtnAction, RedirectController = rtnController });
}
public ActionResult LogonAgain()
{
return null;
}
}
And, the much awaited stack trace:
at System.Web.Mvc.AuthorizeAttribute.AuthorizeCore(HttpContextBase httpContext)
at System.Web.Mvc.AuthorizeAttribute.OnAuthorization(AuthorizationContext filterContext)
at System.Web.Mvc.ControllerActionInvoker.InvokeAuthorizationFilters(ControllerContext controllerContext, IList`1 filters, ActionDescriptor actionDescriptor)
at System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext controllerContext, String actionName)
at System.Web.Mvc.Controller.ExecuteCore()
at System.Web.Mvc.ControllerBase.Execute(RequestContext requestContext)
at System.Web.Mvc.ControllerBase.System.Web.Mvc.IController.Execute(RequestContext requestContext)
at MyCompany.Authentication.AuthenticationManager.Authenticate(String resourceName, ClaimsPrincipal incomingPrincipal) in c:\Development\Give4GoodGallery\ThreeFifteen.Plexus.Web\Authentication\AuthenticationManager.cs:line 63
On closer inspection, I see this looks to be about the AuthorizeAttribute - that might requite some sort of context not present when the Execute is directly called.
Looking at the stack trace it can be seen that the exception is thrown in AuthorizeAttribute.AuthorizeCore. This is a simple method that will access the User property of the HttpContextBase and also the Identity property of this user property. As far as I can see one of these properties must be null for the NullReferenceException to be thrown.
Most likely the User property of HttpContext.Current is null. This does not seem surprising as you are invoking the ErrorController in AuthenticationManager which I assume is part of the authentication process. Before this process is complete the user is not known.
So you could fix your problem by making sure that the user is known before invoking the ErrorController but the real question is why do you require authorization on ErrorController? The best solution is probably to make sure that ErrorController does not require authorization. This will allow you to invoke the controller also when errors occur before or during authorization.
Are u trying to call this method from a running application? or from a unit test or anything else besides asp.net mvc runtime?
if the first is true than here is nothing I can think of that could cause the NRE, but if the second is true then make sure User property is provider with value.
HttpContextBase GetContext(string userName)
{
var user = new GenericPrincipal(new GenericIdentity(userName, "Forms"), new string[] {});
var contextMock = new Mock<HttpContextBase>();
contextMock.Setup(context => context.User).Returns(user);
contextMock.Setup.....
return contextMock.Object;
}
I am able to execute your code without any exception though the exact scenario in your case could be different.
I am not sure, if you did it already, and if you are doing this in Visual Studio then:
1) Wrap the code: controller.Execute(ctx); in a try...catch block so that you should be able to see the exception detail. You can click the "View Detail" at the bottom of exception window so that you can see complete exception details like InnerException,StackTrace etc., within a new window. They may help you to narrow down on the exception.
2) Put a break-point in Index action of ErrorController so that you can step line-by-line by hitting 'F10' to check: If the ErrorController is called or not in first place and also to check, if any of that code throwing exception.
However the basic areas you can cross check are:
1) The code within the "Index" action [you are setting this via:
routeData.Values.Add("action", "Index"); ] might have some uninitialized object that could be throwing.
2) The View you are returning from Index action of ErrorController is existing in a proper View folder.
3) If the Error View is existing as expected, then check within the view if it is not referencing any uninitialized object.
How can I determine which part of RequestContext is the problem that
makes 'Execute' throw NullReferenceException?
Put a breakpoint on the Execute call line
Enable auto breaking on exception in visual studio (Debug -> Exceptions... -> Common Language Runtime Exceptions -> System -> System.NullReferenceException -> 'check the thrown checkbox')
Press F5 to continue program execution and wait for the exception to be raise
Get the stack strace of the exception or open the call stack window (Debug -> Windows -> Call stack)
From the topmost call, you have to isolate the variable that is not initialized on which you are trying to perform an operation. To do this you can use the Auto window (Debug -> Windows -> Auto) or the Watch window (Debug -> Windows -> Watch) to inspect the content of your current variables (not lambdas though) and find which one is null.

Categories

Resources