Unit testing cheat sheet (xUnit, FakeItEasy, FluentAssertions)
Here are some tips, samples, and suggestions for implementing common unit testing tasks.
Unit test without parameters:
1 2 3 4 5 6 7 | [Fact] void SampleClassName_MethodToBeTested_DescriptiveTestTitle() { // ARRANGE // ACT // ASSERT } |
Unit test with parameters:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | [Theory] [InlineData( "abc" , 1, true )] [InlineData( "xyz" , 2, false )] void SampleClassName_MethodToBeTested_DescriptiveTestTitle ( string ? param1, int ? param2, bool ? param3 ) { // ARRANGE // ACT // ASSERT } |
Unit tests with DateTime or DateTimeOffset parameters:
1 2 3 4 5 6 7 8 9 10 11 12 | [Theory] [InlineData( "2024-06-05 23:45:10.456" , "3/28/2007 12:13:50 PM -07:00" )] void SampleClassName_MethodToBeTested_DescriptiveTestTitle ( string ? paramDateTime, string ? paramDateTimeOffset ) { DateTime dateTime = DateTime.Parse(paramDateTime); DateTime dateTimeOffset = DateTimeOffset.Parse(paramDateTimeOffset); ... } |
Unit tests with complex parameters:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | [Theory] [InlineData( "{\"id\":123,\"email\":\"joe.doe@somemail.com\",\"enabled\":true}" )] void SampleClassName_MethodToBeTested_DescriptiveTestTitle ( string ? paramUser ) { // This example uses Newtonsoft.Json, but the same can be done // using the default framework's JSON serializer. User? user = JsonConvert.DeserializeObject<User?>(paramUser); // NOTE: Yes, I know about MemberData, but this seems more straightforward IMHO. ... } |
A fake object with the default constructor (can use an interface or a class):
1 2 3 | ISample fakeISample = A.Fake<ISample>(); Sample fakeSample = A.Fake<Sample>(); ISample<AnotherSample> fakeGenericSample = A.Fake<ISample<AnotherSample>>(); |
A fake object with a parametrized constructor:
1 | Sample fakeSample = A.Fake<Sample>(x => x.WithArgumentsForConstructor( new object [] { "param1" , 2, true })); |
A fake object returns specific property values:
1 2 3 4 5 | User user = A.Fake<User>(); A.CallTo(() => user.Id).Returns(12345); A.CallTo(() => user.Email).Returns( "joe.doe@somemail.com" ); A.CallTo(() => user.Enabled).Returns( true ); |
A fake object returns a specific method result:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // Data service is used by user service to get user from database // and we are faking it. IDataService dataService = A.Fake<IDataService>(); // Define properties of the user object to be retuned. int id = 12345; // Let's assume that the method of the UserService class being tested // internally calls the GetUserById method of the IDataService object // (we're using a fake here to simulate a valid return). A.CallTo(() => dataService.GetUserById(id)).Returns( new User(id)); // UserService is the class we're testing (system under test or SUT). UserService userService = new UserService(dataService); // We are testing the Enable method and expect it to be successful. userService.Enable(id); |
Use wildcard to trigger a fake method result for any parameter value:
1 2 | // A<T>._ is a shortcut for a wildcard. A.CallTo(() => dataService.GetUserById(A< string >._)).Returns(existingUser); |
A fake object returns a specific value from an async method:
1 2 3 4 5 6 7 | IDataService dataService = A.Fake<IDataService>(); // Define properties of the user object to be retuned. int id = 12345; // Assume that GetUserById is an async method returning Task<User>. A.CallTo(() => dataService.GetUserById(A< string >._)).Returns(Task.FromResult( new User(id))); |
A fake object returns a specific value from a generic method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // Data service is used by user service to get user from database // and we are faking it. IDataService dataService = A.Fake<IDataService>(); // Define properties of the user object to be retuned. int id = 12345; // Data service has a generic method GetUser that we want to fake. A.CallTo(dataService).Where(call => call.Method.Name == "GetUser" ) .WithReturnType<User>() .Returns( new User(id)); // UserService is the class we're testing (system under test or SUT). UserService userService = new UserService(dataService); |
Force a fake method to throw an exception:
1 2 3 | A.CallTo(() => dataService.GetUserById(A< string >._)).Throws<Exception>(); // Equivalent to: A.CallTo(() => dataService.GetUserById(A< string >._)).Throws( new Exception()); |
Expect a method to throw an exception of the exact type:
1 | Assert.Throws<InvalidInputException>(() => userService.UpdateUser(user)); |
Expect a method to throw any exception derived from the specific type:
1 | Assert.ThrowsAny<InvalidInputException>(() => userService.UpdateUser(user)); |
Assign expected exception to a variable:
1 | Exception ex = Assert.Throws<InvalidInputException>(() => userService.UpdateUser(user)); |
Set up a fake SendGrid call:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | ISendGridClient _sendGridClient = A.Fake<ISendGridClient>(); System.Net.Http.HttpResponseMessage httpResponse = new (); System.Net.Http.HttpContent httpContent = httpResponse.Content; System.Net.Http.HttpResponseHeaders httpHeaders = httpResponse.Headers; httpHeaders.Add( "X-Message-Id" , "12345" ); SendGrid.Response sendGridResponse = A.Fake<SendGrid.Response>(x => x .WithArgumentsForConstructor( new object [] { httpStatusCode, httpContent, httpHeaders })); A.CallTo(() =< _sendGridClient .SendEmailAsync(A<SendGridMessage>._, A<CancellationToken>._)) .Returns(Task.FromResult(sendGridResponse)); |
Mock HttpContext for a controller class under test:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | System.Net.Http.HttpRequest httpRequest = A.Fake<HttpRequest>(); System.Net.Http.HttpContext httpContext= A.Fake<HttpContext>(); A.CallTo(() => httpContext.Request).Returns(httpRequest); // Set up the request properies that you need. A.CallTo(() => httpRequest.Scheme).Returns( "https" ); A.CallTo(() => httpRequest.Host).Returns( new HostString( "localhost:8888" )); A.CallTo(() => httpRequest.PathBase).Returns( new PathString( "/api/v1" )); A.CallTo(() => httpRequest.Path).Returns( new PathString( "sample" )); A.CallTo(() => httpRequest.QueryString).Returns( new QueryString( "?a=b&c=d" )); // SampleController is derived from the ControllerBase class. SampleController controller = new (...); controller.ControllerContext = new ControllerContext() { HttpContext = httpContext }; |
Controller method GET returns HTTP status code 200 OK:
1 2 3 4 5 6 7 8 9 10 | // Assume that all dependencies have been set. ActionResult<User> actionResult = controller.GetUser( "1234567890" ); // First, test action result. actionResult.Should().NotBeNull(); actionResult.Result.Should().NotBeNull(); actionResult.Result.Should().BeAssignableTo<OkObjectResult>(); // Next, test response specific result. OkObjectResult? result = actionResult.Result as OkObjectResult; |
Controller method POST returns HTTP status code 201 Created:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // Assume that all dependencies have been set. ActionResult<User> actionResult = controller.CreateUser(user); // First, test action result. actionResult.Should().NotBeNull(); actionResult.Result.Should().NotBeNull(); actionResult.Result.Should().BeAssignableTo<CreatedResult>(); // Next, test response specific result. CreatedResult? result = actionResult.Result as CreatedResult; result.Should().NotBeNull(); // Successful POST must return the URL of the GET method // ending with the ID of the newly created object in the // Location header. result?.Location.Should().NotBeNull(); result?.Location.Should().EndWith(id); |
Controller method PATCH returns HTTP status code 204 No Content:
1 2 3 4 5 6 | // Assume that all dependencies have been set. ActionResult actionResult = controller.UpdateUser(user); // First, test action result. actionResult.Should().NotBeNull(); actionResult.BeAssignableTo<NoContentResult>(); |
Controller method POST returns HTTP status code 400 Bad Request:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | // Assume that all dependencies have been set. ActionResult<User> actionResult = controller.CreateUser(user); // First, test action result. actionResult.Should().NotBeNull(); actionResult.Result.Should().NotBeNull(); actionResult.Result.Should().BeAssignableTo<BadRequestObjectResult>(); // Next, test response specific result. BadRequestObjectResult? result = actionResult.Result as BadRequestObjectResult; result.Should().NotBeNull(); // Finally, check the error object to be returned to consumer. // This example shows a custom problem details object ErroDetails, // which may be different in your case. result?.Value.Should().NotBeNull(); result?.Value.Should().BeAssignableTo<ErrorDetails>(); if (result?.Value is ErrorDetails errorDetails) { // ServiceCodeType is a custom enum value returned via the error object's // ServiceCode property (this check may be different in your case). errorDetails.ServiceCode.Should().NotBeNull(); errorDetails.ServiceCode.Should().Be(ServiceCodeType.BadRequest.ToString()); } |
Controller method POST returns HTTP status code 401 Unauthorized:
1 2 3 4 5 6 7 8 9 10 11 12 13 | // Assume that all dependencies have been set. ActionResult<User> actionResult = controller.CreateUser(user); // First, test action result. actionResult.Should().NotBeNull(); actionResult.Result.Should().NotBeNull(); actionResult.Result.Should().BeAssignableTo<UnauthorizedObjectResult>(); // Next, test response specific result. UnauthorizedObjectResult? result = actionResult.Result as UnauthorizedObjectResult; result.Should().NotBeNull(); // See example handling 400 Bad Request. |
Controller method PATCH returns HTTP status code 404 Not Found:
1 2 3 4 5 6 7 8 9 10 11 12 13 | // Assume that all dependencies have been set. ActionResult<User> actionResult = controller.UpdateUser(user); // First, test action result. actionResult.Should().NotBeNull(); actionResult.Result.Should().NotBeNull(); actionResult.Result.Should().BeAssignableTo<NotFoundObjectResult>(); // Next, test response specific result. NotFoundObjectResult? result = actionResult.Result as NotFoundObjectResult; result.Should().NotBeNull(); // See example handling 400 Bad Request. |
Controller method POST returns HTTP status code 409 Conflict:
1 2 3 4 5 6 7 8 9 10 11 12 13 | // Assume that all dependencies have been set. ActionResult<User> actionResult = controller.CreateUser(user); // First, test action result. actionResult.Should().NotBeNull(); actionResult.Result.Should().NotBeNull(); actionResult.Result.Should().BeAssignableTo<ConflictObjectResult>(); // Next, test response specific result. ConflictObjectResult? result = actionResult.Result as ConflictObjectResult; result.Should().NotBeNull(); // See example handling 400 Bad Request. |
Mock AppSettings:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // The following dictionary mimics appsettings.json file. // Notice how array values must be defined using indexes. Dictionary< string , string ?> configSettings = new () { { "ServiceA:ValueSettingX" , "ValueX" }, { "ServiceA:ValueSettingY" , "ValueY" }, { "ServiceA:ValueSettingZ" , "ValueZ" }, { "ServiceA:ArraySetting1:0" , "Value0" }, { "ServiceA:ArraySetting1:1" , "Value1" }, { "ServiceA:ArraySetting1:2" , "Value2" }, } IConfiguration config = new ConfigurationBuilder() .AddInMemoryCollection(configSettings) .Build(); |
Common FluentAssertions:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | // Value should not be null. value.Should().NotBeNull(); // Value should be of specific type. value.Should().BeOfType<User>(); // Value should be equal to. value.Should().Be(1); value.Should().Be( true ); value.Should().Be( "expected value" ); // Value should contain (comparison is case sensitive). value.Should().Contain( "value" ); // Value should contain any one of the specified values (comparison is case sensitive): value.Should().ContainAny( "value1" , "value2" ); // Value should contain all of the specified values (comparison is case sensitive): value.Should().ContainAll( "value1" , "value2" ); // String value should be equal to (comparison is case insensitive). value.Should().BeEquivalentTo( "Value" ); |
No comments:
Post a Comment