I'm trying to learn unit testing in xUnit for ASP.NET Core projects. In order to achieve it, I have created simple ASP.NET Core project to perform tests on it. However I cannot get 100% cover of tests due to wrong testing an exception that's being caught inside controller method.
Here is my controller method I'm testing:
[HttpGet]
public async Task<IEnumerable<User>> GetUsers()
{
try
{
var users = await _repository.User.GetAllUsersAsync();
return users;
}
catch (Exception e)
{
_logger.LogError($"Error in GetUsers: {e}");
return null;
}
}
And here is my unit test method in xUnit:
[Fact]
public async Task GetUsers_WhenCalled_ReturnsCorrectAmountOfUsers()
{
//Arrange
var mockRepo = new Mock<IRepositoryWrapper>();
mockRepo.Setup(repo => repo.User.GetAllUsersAsync())
.ReturnsAsync(GetTestUsers());
var controller = new UsersController(mockRepo.Object, _logger, _service);
//Act
var result = await controller.GetUsers();
//Assert
var model = Assert.IsAssignableFrom<IEnumerable<User>>(result);
model.Count().Should().Be(3);
Assert.Throws<NullReferenceException>(() =>
_controller.GetUsers().Exception);
}
When I run tests, everything gets green status, however inside the controller class I cannot see a 'green tick' next to the lines with catch block scope. I'd really like to know how to write proper code for testing an exceptions inside catch blocks!
Another test is needed that will cause the exception to be thrown when being exercised.
For example
[Fact]
public async Task GetUsers_WhenCalled_HandlesException() {
//Arrange
var mockRepo = new Mock<IRepositoryWrapper>();
mockRepo
.Setup(repo => repo.User.GetAllUsersAsync())
.ThrowsAsync(new InvalidOperationException());
var controller = new UsersController(mockRepo.Object, _logger, _service);
//Act
var result = await controller.GetUsers();
//Assert
Assert.IsNull(result);
//Can also assert what logger records
}
In the above example, when GetAllUsersAsync is invoked, it will throw an exception that will be caught in the try-catch and allow the code to flow as intended for the test.
Related
I have a controller with a simple conditional statement that checks a value and then returns a BadRequest should that value not match what is expected.
public async Task<IActionResult> SendCarDetails(string carValue)
{
try
{
if (carValue== "0")
{
return BadRequest("Car value cannot be 0");
}
}
catch (Exception ex)
{
return StatusCode(500);
}
}
To test this simple condition I wrote a unit test, although it works I feel that it's a little disconnected from the above method and therefore is not sufficient to qualify as a test. This defines the value as being 0 and then checks it, I then create a new BadRequestResult and should that response match 400 then the test is passed.
[TestMethod]
public void SendCarDetails_Returns_400_When_Zero()
{
string paymentValue = "0";
if (paymentValue == "0")
{
var response = new BadRequestResult();
Assert.AreEqual(StatusCodes.Status400BadRequest, response.StatusCode);
}
}
Can someone guide me on writing a unit test for my controller condition or is my approach above accurate?
The shown test above is disconnected from the controller action to be tested.
Ideally you want to create an instance of the subject under test and invoke the target member to assert the expected behavior
For example
[TestMethod]
public async Task SendCarDetails_Returns_400_When_Zero() {
//Arrange
MyController subject = new MyController();
string paymentValue = "0";
//Act
IActionResult result = await subject.SendCarDetails(paymentValue);
BadRequestObjectResult actual = result as BadRequestObjectResult ;
//Assert
Assert.IsNotNull(actual);
}
I developed Unit test for my service. Now my test check on inserting Name and 2nd check on a null.
[TestMethod]
public async Task InsertTownName_ReturnTownName()
{
var builder = new RepositoryBuilder<TownRepository>().SetupManager();
using (builder.CreateIsolationScope())
{
var repository = builder.Build();
var townName = "Town" + DateTime.UtcNow.ToString();
await repository.InsertTown(townName);
var connection = builder.TransactionManager.Connection;
var transaction = builder.TransactionManager.Transaction;
var result = await connection.ReadSingleOrDefaultAsync<Town>(x => x.Name == townName, transaction: transaction);
Assert.IsNotNull(result);
Assert.AreEqual(townName, result.Name);
}
}
[TestMethod]
public async Task InsertNullName()
{
var builder = new RepositoryBuilder<TownRepository>().SetupManager();
using (builder.CreateIsolationScope())
{
var repository = builder.Build();
await repository.InsertTown(null);
var connection = builder.TransactionManager.Connection;
var transaction = builder.TransactionManager.Transaction;
var result = await connection.ReadSingleOrDefaultAsync<Town>(x => x.Name == null, transaction: transaction);
Assert.IsNull(result.Name);
Assert.AreEqual(null, result.Name);
}
}
Both of this method works good. Next step I need check on Empty (If in line where user need insert town name - empty name). I have no idea how implement it. Could You recommend to me and could you check 2nd method for working with null. Is it correct unit test? Here my method that I tested
public async Task<int> InsertTown(string townName)
if (String.IsNullOrEmpty(townName))
{
throw new Exception();
}
else
{
var parameters = new { townName };
return await Connection.QueryFirstOrDefaultAsync<int>(Adhoc["AddTown"], parameters, Transaction);
}
Here is an example of what a method with input parameters checking might look like.
public async Task<int> InsertTown(string townName)
{
if (townName == null)
{
throw new ArgumentNullException(nameof(townName));
}
if (string.IsNullOrWhiteSpace(townName))
{
throw new ArgumentException("Parameter cannot be empty", nameof(townName));
}
var parameters = new { townName };
//return await Connection.QueryFirstOrDefaultAsync<int>(Adhoc["AddTown"], parameters, Transaction);
return await Task.Run(() => 42); // for testing puprposes
}
You can test it like this
[TestMethod]
public async Task InsertTown_NullString_ThrowsArgumentNullExceptionAsync()
{
string townName = null;
// var repository = ...;
await Assert.ThrowsExceptionAsync<ArgumentNullException>(() =>
repository.InsertTown(townName));
}
[DataTestMethod]
[DataRow("")]
[DataRow(" ")]
public async Task InsertTown_EmptyString_ThrowsArgumentExceptionAsync(string value)
{
string townName = value;
// var repository = ...;
await Assert.ThrowsExceptionAsync<ArgumentException>(() =>
repository.InsertTown(townName));
}
As mentioned in the comments, it looks like your unit tests are more like integration tests.
In unit tests, you should not access neither real database, work with a network, nor work with a file system.
You have to mock any dependencies. For example with NSubstitute, FakeItEasy or Moq.
Your Connection.QueryFirstOrDefaultAsync method is static, right?
It is highly recommendable to rewrite the code, getting rid of static methods. With the extraction of abstractions to interfaces. Because static methods, like in your case, are untested or difficult to test.
I am a newbie to XUnit and Moq. I have a method which takes string as an argument.How to handle an exception using XUnit.
[Fact]
public void ProfileRepository_GetSettingsForUserIDWithInvalidArguments_ThrowsArgumentException() {
//arrange
ProfileRepository profiles = new ProfileRepository();
//act
var result = profiles.GetSettingsForUserID("");
//assert
//The below statement is not working as expected.
Assert.Throws<ArgumentException>(() => profiles.GetSettingsForUserID(""));
}
Method under test
public IEnumerable<Setting> GetSettingsForUserID(string userid)
{
if (string.IsNullOrWhiteSpace(userid)) throw new ArgumentException("User Id Cannot be null");
var s = profiles.Where(e => e.UserID == userid).SelectMany(e => e.Settings);
return s;
}
The Assert.Throws expression will catch the exception and assert the type. You are however calling the method under test outside of the assert expression and thus failing the test case.
[Fact]
public void ProfileRepository_GetSettingsForUserIDWithInvalidArguments_ThrowsArgumentException()
{
//arrange
ProfileRepository profiles = new ProfileRepository();
// act & assert
Assert.Throws<ArgumentException>(() => profiles.GetSettingsForUserID(""));
}
If bent on following AAA you can extract the action into its own variable.
[Fact]
public void ProfileRepository_GetSettingsForUserIDWithInvalidArguments_ThrowsArgumentException()
{
//arrange
ProfileRepository profiles = new ProfileRepository();
//act
Action act = () => profiles.GetSettingsForUserID("");
//assert
ArgumentException exception = Assert.Throws<ArgumentException>(act);
//The thrown exception can be used for even more detailed assertions.
Assert.Equal("expected error message here", exception.Message);
}
Note how the exception can also be used for more detailed assertions
If testing asynchronously, Assert.ThrowsAsync follows similarly to the previously given example, except that the assertion should be awaited,
public async Task Some_Async_Test() {
//...
//Act
Func<Task> act = () => subject.SomeMethodAsync();
//Assert
var exception = await Assert.ThrowsAsync<InvalidOperationException>(act);
//...
}
If you do want to be rigid about AAA then you can use Record.Exception from xUnit to capture the Exception in your Act stage.
You can then make assertions based on the captured exception in the Assert stage.
An example of this can be seen in xUnits tests.
[Fact]
public void Exception()
{
Action testCode = () => { throw new InvalidOperationException(); };
var ex = Record.Exception(testCode);
Assert.NotNull(ex);
Assert.IsType<InvalidOperationException>(ex);
}
It's up to you what path you want to follow, and both paths are fully supported by what xUnit provides.
You could consider something like this if you want to stick to AAA:
// Act
Task act() => handler.Handle(request);
// Assert
await Assert.ThrowsAsync<MyExpectedException>(act);
I think there are two way to handle this scenario which I personally like. Suppose I have below method which I want to test
public class SampleCode
{
public void GetSettingsForUserID(string userid)
{
if (string.IsNullOrWhiteSpace(userid)) throw new ArgumentException("User Id
Cannot be null");
// Some code
}
}
I can test this with below testcase,Make sure you add FluentAssertions nuget in your test project.
public class SampleTest
{
private SampleCode _sut;
public SampleTest()
{
_sut = new SampleCode();
}
[Theory]
[InlineData(null)]
[InlineData(" ")]
public void TestIfValueIsNullorwhiteSpace(string userId)
{
//Act
Action act= ()=> _sut.GetSettingsForUserID(userId);
// Assert
act.Should().ThrowExactly<ArgumentException>().WithMessage("User Id Cannot be null");
}
}
but I found one problem here, whitespace and Null are two different things.
c# provides ArgumentException for whitespace and ArgumentNullException for null reference.
So You can refactor your code something like this
public void GetSettingsForUserID(string userid)
{
Guard.Against.NullOrWhiteSpace(userid, nameof(userid));
}
Here you need Ardalis.GuardClauses nuget in your code project And testcase will be something like this
[Fact]
public void TestIfValueIsNull()
{
//Act
Action act = () => _sut.GetSettingsForUserID(null);
//Assert
act.Should().ThrowExactly<ArgumentNullException>().WithMessage("*userId*");
}
[Fact]
public void TestIfValueIsWhiteSpace()
{
//Act
Action act= ()=> _sut.GetSettingsForUserID(" ");
//Assert
act.Should().ThrowExactly<ArgumentException>().WithMessage("*userId*");
}
Instead of following complicated protocols I found it most convenient to use a try catch block :
try
{
var output = Settings.GetResultFromIActionResult<int>(controller.CreateAllFromExternalAPI());
Assert.True(output > 0);
}
catch(InvalidOperationException e)
{
Assert.True("Country table can only be filled from ExternalAPI if table is blank"==e.Message);
}
I have a WebAPI that uses a custom ExceptionHandler to handle all exceptions. How can I unit test this CustomExceptionHandler. Any lead will be helpful
public class CustomExceptionHandler : ExceptionHandler
{
public override void Handle(ExceptionHandlerContext context)
{
try
{
context.Result = new ResponseMessageResult(context.Request.CreateResponse(HttpStatusCode.InternalServerError, context.Exception));
}
catch (Exception)
{
base.Handle(context);
}
}
public override bool ShouldHandle(ExceptionHandlerContext context)
{
return true;
}
}
To unit test this custom exception handler create the dependencies needed by the sut/mut and exercise the test to verify the expected behavior.
Here is a simple example to get you started.
[TestClass]
public class CustomExcpetionhandlerUnitTests {
[TestMethod]
public void ShouldHandleException() {
//Arrange
var sut = new CustomExceptionHandler();
var exception = new Exception("Hello World");
var catchblock = new ExceptionContextCatchBlock("webpi", true, false);
var configuration = new HttpConfiguration();
var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/api/test");
request.SetConfiguration(configuration);
var exceptionContext = new ExceptionContext(exception, catchblock, request);
var context = new ExceptionHandlerContext(exceptionContext);
Assert.IsNull(context.Result);
//Act
sut.Handle(context);
//Assert
Assert.IsNotNull(context.Result);
}
}
For the above test, only the necessary dependencies were provided in order to exercise the test. The method under test (mut) had one dependency on ExceptionHandlerContext. The minimal dependencies of this class for the test was provided to it before passing it to the mut.
The assertions can be expanded to suit the expected behaviors.
Since none of the dependencies were abstract, Moq would have been unable to wrap them. However this did not stop the manual instantiation of the classes needed.
I'm trying to unit test a controller that is catching a FlurlHttpException and calling GetResponseJson<TError>() to get the error message in the catch block. I attempted to mock the exception, but the Call property does not allow me set the Settings. When the unit test runs it fails because there isn't a JsonSerializer in the settings. How do I setup this test?
Here's my current attempt that does not work:
Controller
[Route]
public async Task<IHttpActionResult> Post(SomeModel model)
{
try
{
var id = await _serviceClient.Create(model);
return Ok(new { id });
}
catch (FlurlHttpException ex)
{
if (ex.Call.HttpStatus == HttpStatusCode.BadRequest)
return BadRequest(ex.GetResponseJson<BadRequestError>().Message);
throw;
}
}
Unit Test
[TestMethod]
public async Task Post_ServiceClientBadRequest_ShouldReturnBadRequestWithMessage()
{
//Arrange
string errorMessage = "A bad request";
string jsonErrorResponse = JsonConvert.SerializeObject(new BadRequestError { Message = errorMessage });
var badRequestCall = new HttpCall
{
Response = new HttpResponseMessage(HttpStatusCode.BadRequest),
ErrorResponseBody = jsonErrorResponse
//This would work, but Settings has a private set, so I can't
//,Settings = new FlurlHttpSettings { JsonSerializer = new NewtonsoftJsonSerializer(new JsonSerializerSettings()) }
};
_mockServiceClient
.Setup(client => client.create(It.IsAny<SomeModel>()))
.ThrowsAsync(new FlurlHttpException(badRequestCall, "exception", new Exception()));
//Act
var result = await _controller.Post(new SomeModel());
var response = result as BadRequestErrorMessageResult;
//Assert
Assert.IsNotNull(response);
Assert.AreEqual(errorMessage, response.Message);
}
If you are encapsulating the usage of Flurl within your ServiceClient object, then I think catching FlurlException, extracting Message, and returning a more appropriate exception should also be encapsulated in that service. This will make your controller much easier to test.