Asserting that a system under test should throw an assertion exception - c#

I am creating an extension method that performs a test on an object to see if it has a specific custom attribute.
I want to create a unit test for my extension method. How can I assert that the test in the extension method should fail?
[Test]
public void ShouldFailIfEmailAttributeMissingFromFieldName()
{
//--Arrange
var model = new { Field = 1 };
//--Act
model.ShouldValidateTheseFields(new List<FieldValidation>
{
new EmailAddressFieldValidation
{
ErrorId = 1,
ErrorMessage = "Message",
FieldName = nameof(model.Field)
}
});
//--Assert
}
Basically, the ShouldValidateTheseFields does reflection and asserts that it should have a custom attribute on the field named "Field" and I need to assert that it failed.

Catch the expected exception. If none is thrown the test fails
[Test]
public void ShouldFailIfEmailAttributeMissingFromFieldName() {
//--Arrange
var model = new { Field = 1 };
//--Act
try {
model.ShouldValidateTheseFields(new List<FieldValidation> {
new EmailAddressFieldValidation {
ErrorId = 1,
ErrorMessage = "Message",
FieldName = nameof(model.Field)
}
});
} catch(MyExpectedException e) {
return;
}
//--Assert
Assert.Fail();
}
Depending on the test framework being used there should be a way for you to assert expected exceptions for a test, which would basically follow a similar format above under the hood.

Create a new custom exception and have it throw if it is missing the custom attribute:
[Test]
public void ShouldFailIfEmailAddressAttributeIsMissingFromFieldName()
{
//--Arrange
var model = new { Field = 1 };
//--Act
Should.Throw<EmailAddressAttributeNotFoundException>(() => model.ShouldValidateTheseFields(
new List<FieldValidation>
{
new EmailAddressFieldValidation
{
ErrorId = 1,
ErrorMessage = "Message",
FieldName = nameof(model.Field)
}
}));
}
To check if an assertion fails you need to catch the Assertion exception. In this case since the Shouldly Framework is being used, it is a Shouldly.ShouldAssertException which is being thrown in the extension method:
[Test]
public void ShouldFailIfEmailAddressAttributeHasWrongErrorId()
{
//--Arrange
var model = new TestModelTwo();
//--Act
Should.Throw<ShouldAssertException>(() => model.ShouldValidateTheseFields(
new List<FieldValidation>
{
new EmailAddressFieldValidation
{
ErrorId = 2,
ErrorMessage = "Message",
FieldName = nameof(model.Field)
}
}));
}
Using the class:
public class TestModel
{
[EmailAddress(1)]
public string Field { get; set; }
}
The failing assertion in the extension method is ErrorId.ShouldBe(2) when it is actually 1 on the model.

Related

N-Tier Architecture WebApi: NUnit - No suitable constructor was found

I'm extremely new in this unit testing and NUnit testing.
When I'm trying to run below code... I'm getting the following error
namespace NC_BLUnitTests
{
[TestFixture]
public class ProductBLTests
{
private IBLProductsRepo blRepo;
public ProductBLTests(IBLProductsRepo _blRepo)
{
blRepo = _blRepo;
}
[Test]
public void AddProduct_Test()
{
//Arrange
var expectedResult = new ProductVM()
{
Name = "prod 4",
CreatedDateTime = DateTime.Now,
Price = 65.89m,
Status = (int)StatusEnm.Active
};
// Act
int resId = blRepo.AddProduct(expectedResult).Result.Data;
var res = blRepo.GetProduct(resId);
// Assert
Assert.Equals(expectedResult, res);
}
}}
No suitable constructor was found
Now, I searched many articles here but I don't understand anything at all.
Here is my business layer code that I'm trying to test
namespace NC_BLRepositories {
public class BLProductsRepo : IBLProductsRepo
{
private IDLProductsRepo dlProductsRepo;
public BLProductsRepo(IDLProductsRepo dlRepo)
{
dlProductsRepo = dlRepo;
}
public Task<Response<int>> AddProduct(ProductVM product)
{
Response<int> res = new Response<int>();
try
{
res.Data = dlProductsRepo.AddProduct(product).Result;
res.IsSuccess = true;
res.Message = "Product Added Successfully";
}
catch (Exception ex)
{
res.IsSuccess = false;
res.Message = "Some Error While Adding Product: " + ex.Message;
}
return Task.FromResult(res);
}
public Task<Response<ProductVM>> GetProduct(int productId)
{
Response<ProductVM> res = new Response<ProductVM>();
try
{
res.IsSuccess = true;
res.Message = "Product fetched successfully";
res.Data = new ProductVM(dlProductsRepo.GetProduct(productId).Result);
if (res.Data?.Id == 0)
res.Message = "Invalid Product";
}
catch (Exception ex)
{
res.IsSuccess = false;
res.Message = "Some Error While Fetching Single Product: " + ex.Message;
}
return Task.FromResult(res);
}
}}
and this is the Data Layer that is injected here and will go for testing in future.
namespace NC_DLRepositories {
public class DLProductsRepo : IDLProductsRepo
{
private MyShopContext dbCtx;
public DLProductsRepo(MyShopContext ctx)
{
dbCtx = ctx;
}
public async Task<int> AddProduct(Product product)
{
Product newProduct = product;
dbCtx.Products.Add(newProduct);
await dbCtx.SaveChangesAsync();
return newProduct.Id;
}
public async Task<Product> GetProduct(int productId)
{
return await dbCtx.Products.Where(e => e.Id == productId).SingleOrDefaultAsync();
}
}}
Please guide me, I have 0 knowledge of all these things and the internet is giving me all the basic articles only.
The error is in the fact that your TestFixture requires an argument of Type IBLProductsRepo in its constructor but none is provided.
By default, when no args are given, NUnit tries to use the default constructor so the message indicates that there is no default constructor. That's slightly misleading, since you really want to provide an argument.
Arguments are provided to NUnit TestFixtures in various ways. The simplest is to use [TestFixture(ARG)] but that is not allowed in C# for a non-primitive class Type. Next best would be to use [TestFixtureSource("xxx")], where xxx is the name of a static field, property or method returning IEnumerable<IBLProductsRepo>.
Alternatively, if you are using the same repo for all tests, remove the argument and make it a singleton, which is accessed by the tests.

Create response body in ObjectResult

I'm a beginner in ASP.NET Core, just a question on ObjectResult. I saw sone code like this:
public override void OnException(ExceptionContext context)
{
var myerror = new
{
Success = false,
Errors = new [] { context.Exception.Message }
};
context.Result = new ObjectResult(myerror)
{
StatusCode = 500
};
context.ExceptionHandled = true;
...
}
my questions are:
1- is the property 'Errors' anonymous type of object "myerror" creating the response body with the exception's message?
It seems that I can use any names for properties not just 'Success; and 'Errors' so can I code like:
var myerror = new
{
mySuccess = false,
myErrors = new [] { context.Exception.Message }
};
is it OK?
whats the purpose of doing context.ExceptionHandled = true;? the book says Marks the exception as handled to prevent it propagating out of MvcMiddleware. But why it need to prevent it propagating out?
1) Yes.
2) Yes, it is okay. You can create object of any structure you want, not only anonymous objects.
public class ErrorModel
{
public string Error { get; set; }
public int Id { get; set; }
public List<int> Values { get; set; }
}
//filter
var error = new ErrorModel
{
Error = context.Exception.Message,
Id = 1,
Values = new List<int> { 1, 2, 3 }
};
context.Result = new ObjectResult(error)
{
StatusCode = 500
};
3) It is possible that there are multiple exception filters in your application and if you don't set ExceptionHandled to true while processing an exception then every filter get called and Result is overriden. The purpose of this property is to indicate that certain filter was able to cope with exception and there is no need to run other exception filters. It is useful in scenarious when filter can handle only certain types of exception.
//handles only exceptions caused by dividing by zero
public class DivideByZeroExceptionFilterAttribute : Attribute, IExceptionFilter
{
public void OnException(ExceptionContext context)
{
//chech if this is divide by zero exception
if (!(context.Exception is DivideByZeroException))
return;
var myerror = new
{
result = false,
message = "Division by zero went wrong"
};
context.Result = new ObjectResult(myerror)
{
StatusCode = 500
};
//set "handled" to true since exception is already property handled
//and there is no need to run other filters
context.ExceptionHandled = true;
}
}

C# MVC Model is always valid when Unit Testing a Controller Action

I am very beginner in this C# world.
There is this model class:
public class Fund
{
[Required]
public int id { get; set; }
[Required]
public string name { get; set; }
[Required]
public string nickname { get; set; }
}
Although the [Required] annotation is over all properties, an instance like:
Fund f = new Fund();
f.name = "test name";
f.nickname = "test ninckname";
always pass in a test like:
if (ModelState.IsValid)
{
// do stuff
}
How am I supposed to set the model such that an instance like that won't pass in in the ModelState.IsValid test?
Other instances like:
Fund f1 = new Fund();
f1.id = 3;
f1.nickname = "test ninckname";
and
Fund f2 = new Fund();
f2.id = 3;
f2.name = "test name";
are also passing on the test.
EDIT:
The ModelState.IsValid is inside a controller, I am testing the controller actually.
EDIT 2:
That is the controller's method signature:
[HttpPatch]
public ActionResult EditFund(Fund fund)
EDIT 3:
That is my whole test method:
[TestMethod]
public void TestEditInvalidFund()
{
// Arrange
FundController controller = new FundController();
controller.ControllerContext = TestModelHelper.AdminControllerContext();
var _fund = new Mock<IFund>();
_fund.SetupGet(f => f.id).Returns(1);
//_fund.SetupGet(f => f.name).Returns("Fund name");
_fund.SetupGet(f => f.nickname).Returns("Fund nickname");
_fund.Setup(f => f.Edit()).Callback(() => {}).Verifiable();
// Act
var result = (JsonResult)controller.EditFund(_fund.Object);
// Assert
SimpleMessage resultMessage = m_serializer.Deserialize<SimpleMessage>(m_serializer.Serialize(result.Data));
Assert.IsNotNull(resultMessage.status, "JSON record does not contain 'status' required property.");
Assert.IsTrue(resultMessage.status.Equals("fail"), "status must be 'fail'");
}
And that is the whole controller's method:
[HttpPatch]
public ActionResult EditFund(IFund _fund)
{
try
{
if (ModelState.IsValid)
{
//_fund.Edit();
}
else
{
string error_messages = "";
foreach (var e in ModelState.Select(x => x.Value.Errors).Where(y => y.Count > 0).ToList())
{
error_messages += e[0].ErrorMessage + "\n";
}
throw new Exception(error_messages);
}
return MessageHelper(#Resources.Global.success, #Resources.Funds.success_editing_fund, "success");
}
catch (Exception err)
{
return ErrorHelper(#Resources.Global.error, #Resources.Funds.error_editing_fund, err.Message);
}
}
You can't unit test ModelState.IsValid. Well, you can, but you need extra code to do it, and it's not exactly ideal.
Here's my code on github: WithModelStateIsInvalid<TController>()
And here's the NuGet package it's in: https://www.nuget.org/packages/TestBase-Mvc/
And here's how I use it:
UnitUnderTest
.WithModelStateIsInValid()
.Action(model)
.ShouldBeViewResult()
.ShouldBeAnInvalidModel();
—————————————————————————————————————————
The reason you can't unit test it, is that Model Validation happens in the ModelBinding step before your controller action is called.
So to simulate a model validation failure, the easiest way is to invalidate controller.ModelState 'manually' in your unit test code, before calling the action. Which is what the extension method does, but actually it's just one-line:
controller.ModelState.AddModelError("FieldName", #"error message")
(A more sophisticated extension method would probably let you specify which Model key is invalid. PRs always welcome).
[Required]
public int id { get; set; }
This will always be valid since an int is not nullable. It has a default value of 0.
Not sure, why it gets valid, but you can try to explicitly revalidate the model by:
TryValidateModel(fund)

Mocking a service call the object returns null

Hello :) I'm a novice in using Moq framework with Unit and I have an issue in which, as I will demonstrate below, I'm trying to Moq a service call on a MVC Controller which takes Session objects as parameters.
On my Unit test framework I create my object, set it up on the service call and I'm hoping to have it as the result of the response of the test to then Assert.
Problem: I tried to Mock HttpContext based on other solutions, which works because on the Controller side I get the values that I set on my Unit Test but upon SETUP of the service call (I have "Mock(MockBehavior.Strict);") when the debugger reaches the controller, upon the actual call I get an error saying that no SETUP was defined. Or if I take out the "MockBehavior.Strict", the "model" variable on the controller is always returning null and not the object I set it on the Unit Test class.
So here is my simple unit class,
[TestClass]
public class SearchControllerTest
{
#region Variables
Mock<ControllerContext> _controllerContext;
Mock<ISearchService> _serviceMock;
SearchController _controller;
#endregion
[TestInitialize]
public void SetUp()
{
// Arrange
_controllerContext = new Mock<ControllerContext>();
_serviceMock = new Mock<ISearchService>(MockBehavior.Strict);
_controller = new SearchController(_serviceMock.Object);
}
#region Success Test Cases
[TestMethod]
public void SearchListTest()
{
string pid = "val1";
string oid = "val2";
string lang = "val3";
string tid = "val4";
string pattern = "val5";
DocumentViewModel docModel = SetDocumentViewModel();
// Bypass
//_controllerContext.Setup(x => x.HttpContext.Session).Returns(_session.Object);
_controllerContext.SetupGet(p => p.HttpContext.Session["ProjectId"]).Returns("X");
_controllerContext.SetupGet(p => p.HttpContext.Session["OverlayId"]).Returns(string.Empty);
_controllerContext.SetupGet(p => p.HttpContext.Session["ProjectLanguage"]).Returns(string.Empty);
_controllerContext.SetupGet(p => p.HttpContext.Session["NodeId"]).Returns(string.Empty);
_controller.ControllerContext = _controllerContext.Object;
_serviceMock.Setup(x => x.FullTextSearchForAll(pid, oid, lang, tid, pattern)).Returns(docModel);
// Act
var result = _controller.SearchList(pid, oid, lang, tid, pattern) as PartialViewResult;
// Assert
Assert.AreEqual("#0Id", ((DocumentViewModel)result.Model).Rows[0].UID);
}
#endregion
#region Private
DocumentViewModel SetDocumentViewModel()
{
return new DocumentViewModel()
{
Columns = new Service.QueryResultColumn[]
{
new Service.QueryResultColumn
{
Alignment = ServiceConstants.Left,
Index = 0,
Visible = true,
Width = 3,
Header = ServiceConstants.Label
}
},
Properties = new DocumentsInfo[]
{
new DocumentsInfo()
{
IsCheckInAllowed = true,
IsCheckoutAllowed = true,
IsDocumentCheckedOut = false,
IsPlaceHolder = false,
IsUndoCheckoutAllowed = true,
lastVersionUid = "123"
}
},
Rows = new Service.QueryResultRow[]
{
new Service.QueryResultRow()
{
Children = null,
ExtensionData = null,
ImageSource = "Source",
Items = new Service.QueryResultItem[]
{
new Service.QueryResultItem()
{
ExtensionData = null,
ImageSource = "Src",
Text = "Txt",
UID = "uid"
}
},
UID = "#0Id"
}
}
};
}
#endregion
}
And here's my Controller,
public class SearchController : Controller
{
ISearchService _searchService;
public SearchController(ISearchService searchService) // I use UnityContainer
{
_searchService = searchService;
}
public PartialViewResult SearchList(string pid, string oid, string lang, string tid, string pattern)
{
ViewBag.ProjectId = pid;
ViewBag.OverlayId = oid;
ViewBag.ProjectLanguage = lang;
ViewBag.NodeId = tid;
ViewBag.Pattern = pattern;
DocumentViewModel model = null;
try
{
model = _searchService.FullTextSearchForAll(
Session["ProjectId"] as string,
Session["OverlayId"] as string,
Session["ProjectLanguage"] as string,
Session["ProjectId"] as string,
pattern
);
}
catch (Exception ex)
{
ViewBag.Error = ex.Message;
}
// Ajax.OnError() will handle the Custom Exception Error Message
if (ViewBag.Error != null)
throw new CustomtException((String)ViewBag.Error);
return PartialView(model);
}
}
Tank your for your patience and time.
Have a nice day :)
You've setup params in method with some values:
_serviceMock.Setup(x => x.FullTextSearchForAll(pid, oid, lang, tid, pattern)).Returns(docModel);
and trying to give Session variable as empty string
_controllerContext.SetupGet(p => p.HttpContext.Session["OverlayId"]).Returns(string.Empty);
it will never match. try setup service with It.IsAny() like
_serviceMock.Setup(x => x.FullTextSearchForAll(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())).Returns(docModel);
And if it will shout change session setup
I recommend creating consts for ProjectId, et. al., and then using them to setup your mocks, verify calls, and set the state of any objects. This ensures the value you expect (and only the value you expect) is used throughout.

How can I test ModelState?

How can I test Controller.ViewData.ModelState? I would prefer to do it without any mock framework.
You don't have to use a Mock if you're using the Repository Pattern for your data, of course.
Some examples:
http://www.singingeels.com/Articles/Test_Driven_Development_with_ASPNET_MVC.aspx
// Test for required "FirstName".
controller.ViewData.ModelState.Clear();
newCustomer = new Customer
{
FirstName = "",
LastName = "Smith",
Zip = "34275",
};
controller.Create(newCustomer);
// Make sure that our validation found the error!
Assert.IsTrue(controller.ViewData.ModelState.Count == 1,
"FirstName must be required.");
//[Required]
//public string Name { get; set; }
//[Required]
//public string Description { get; set; }
ProductModelEdit model = new ProductModelEdit() ;
//Init ModelState
var modelBinder = new ModelBindingContext()
{
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(
() => model, model.GetType()),
ValueProvider=new NameValueCollectionValueProvider(
new NameValueCollection(), CultureInfo.InvariantCulture)
};
var binder=new DefaultModelBinder().BindModel(
new ControllerContext(),modelBinder );
ProductController.ModelState.Clear();
ProductController.ModelState.Merge(modelBinder.ModelState);
ViewResult result = (ViewResult)ProductController.CreateProduct(null,model);
Assert.IsTrue(result.ViewData.ModelState["Name"].Errors.Count > 0);
Assert.True(result.ViewData.ModelState["Description"].Errors.Count > 0);
Assert.True(!result.ViewData.ModelState.IsValid);
For testing Web API, use the Validate method on the controller:
var controller = new MyController();
controller.Configuration = new HttpConfiguration();
var model = new MyModel();
controller.Validate(model);
var result = controller.MyMethod(model);
Ran into this problem for .NetCore 2.1
Here's my solution:
Extension Method
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
namespace MyExtension
{
public static void BindViewModel<T>(this Controller controller, T model)
{
if (model == null) return;
var context = new ValidationContext(model, null, null);
var results = new List<ValidationResult>();
if (!Validator.TryValidateObject(model, context, results, true))
{
controller.ModelState.Clear();
foreach (ValidationResult result in results)
{
var key = result.MemberNames.FirstOrDefault() ?? "";
controller.ModelState.AddModelError(key, result.ErrorMessage);
}
}
}
}
View Model
public class MyViewModel
{
[Required]
public string Name { get; set; }
}
Unit Test
public async void MyUnitTest()
{
// helper method to create instance of the Controller
var controller = this.CreateController();
var model = new MyViewModel
{
Name = null
};
// here we call the extension method to validate the model
// and set the errors to the Controller's ModelState
controller.BindViewModel(model);
var result = await controller.ActionName(model);
Assert.NotNull(result);
var viewResult = Assert.IsType<BadRequestObjectResult>(result);
}
This not only let's you check that the error exists but also checks that it has the exact same error message as expected. For example both of these parameters are Required so their error message shows as "Required".
Model markup:
//[Required]
//public string Name { get; set; }
//[Required]
//public string Description { get; set; }
Unit test code:
ProductModelEdit model = new ProductModelEdit() ;
//Init ModelState
var modelBinder = new ModelBindingContext()
{
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(
() => model, model.GetType()),
ValueProvider=new NameValueCollectionValueProvider(
new NameValueCollection(), CultureInfo.InvariantCulture)
};
var binder=new DefaultModelBinder().BindModel(
new ControllerContext(),modelBinder );
ProductController.ModelState.Clear();
ProductController.ModelState.Merge(modelBinder.ModelState);
ViewResult result = (ViewResult)ProductController.CreateProduct(null,model);
Assert.IsTrue(!result.ViewData.ModelState.IsValid);
//Make sure Name has correct errors
Assert.IsTrue(result.ViewData.ModelState["Name"].Errors.Count > 0);
Assert.AreEqual(result.ViewData.ModelState["Name"].Errors[0].ErrorMessage, "Required");
//Make sure Description has correct errors
Assert.IsTrue(result.ViewData.ModelState["Description"].Errors.Count > 0);
Assert.AreEqual(result.ViewData.ModelState["Description"].Errors[0].ErrorMessage, "Required");
Adding to the great answers above, check out this fantastic use of the protected TryValidateModel method within the Controller class.
Simply create a test class inheriting from controller and pass your model to the TryValidateModel method. Here's the link:
http://blog.icanmakethiswork.io/2013/03/unit-testing-modelstate.html
Full credit goes to John Reilly and Marc Talary for this solution.

Categories