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.
Related
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.
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)
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.
This is really a generic (and probably a more subjective too) question. I have some classes where I use an interface to define a standard approach to validating the object state. When I did this, I got to scratching my head... is it best to
1.) allow the constructor (or initializing method) to silently filter out the errant information automatically or...
2.) allow the client to instantiate the object however and let the client also call the interface's IsValid property or Validate() method before moving forward?
Basically one approach is silent but could be misleading in that the client may not be aware that certain pieces of information were filtered away due to it not meeting the validation criteria. The other approach then would be more straight forward, but also adds a step or two? What's typical here?
Okay, after a long day of trying to keep up with some other things, I finally did come up with an example. Please for me for it as it's not ideal and by no means something wonderful, but hopefully should serve well enough to get the point across. My current project is just too complicated to put something simple out for this, so I made something up... and trust me... totally made up.
Alright, the objects in the example are this:
Client: representing client-side code (Console App btw)
IValidationInfo: This is the actual interface I'm using in my current project. It allows me to create a validation framework for the "back-end" objects not necessarily intended for the Client to use since the business logic could be complicated enough. This also allowed me to separate validation code and call as-needed for the business logic.
OrderManager: This is an object the client-side code can use to manage their orders. It's client-friendly so-to-speak.
OrderSpecification: This is an object the client-side code can use to request an order. But if the business logic doesn't work out, an exception can be raised (or if necessary the order not added and exceptions ignored...) In my real-world example I actually have an object that's not quite so black-and-white as to which side of this fence it goes... thus my original question when I realized I could push validation request (calling IsValid or Validate()) to the cilent.
CustomerDescription: represents customers to which I've classified (pretending to have been read from a DB.
Product: Represents a particular product which is classified also.
OrderDescription: Represents the official order request.The business rule is that the Customer cannot order anything to which they've not been classified (I know.. that's not very real-world, but it gave me something to work with...)
Ok... I just realized I can't attach a file here, so here's the code. I apologize for it's lengthy appearance. That was the best I could do to create a client-friendly front-end and business logic back-end using my Validation interface:
public class Client
{
static OrderManager orderMgr = new OrderManager();
static void Main(string[] args)
{
//Request a new order
//Note: Only the OrderManager and OrderSpecification are used by the Client as to keep the
// Client from having to know and understand the framework beyond that point.
OrderSpecification orderSpec = new OrderSpecification("Customer1", new Product(IndustryCategory.FoodServices, "Vending Items"));
orderMgr.SubmitOrderRequest(orderSpec);
Console.WriteLine("The OrderManager has {0} items for {1} customers.", orderMgr.ProductCount, orderMgr.CustomerCount);
//Now add a second item proving that the business logic to add for an existing customer works
Console.WriteLine("Adding another valid item for the same customer.");
orderSpec = new OrderSpecification("Customer1", new Product(IndustryCategory.FoodServices, "Sodas"));
orderMgr.SubmitOrderRequest(orderSpec);
Console.WriteLine("The OrderManager now has {0} items for {1} customers.", orderMgr.ProductCount, orderMgr.CustomerCount);
Console.WriteLine("Adding a new valid order for a new customer.");
orderSpec = new OrderSpecification("Customer2", new Product(IndustryCategory.Residential, "Magazines"));
orderMgr.SubmitOrderRequest(orderSpec);
Console.WriteLine("The OrderManager now has {0} items for {1} customers.", orderMgr.ProductCount, orderMgr.CustomerCount);
Console.WriteLine("Adding a invalid one will not work because the customer is not set up to receive these kinds of items. Should get an exception with message...");
try
{
orderSpec = new OrderSpecification("Customer3", new Product(IndustryCategory.Residential, "Magazines"));
orderMgr.SubmitOrderRequest(orderSpec);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.ReadLine();
}
}
public interface IValidationInfo
{
string[] ValidationItems { get; }
bool IsValid { get; }
void Validate();
List<string> GetValidationErrors();
string GetValidationError(string itemName);
}
public class OrderManager
{
private List<OrderDescription> _orders = new List<OrderDescription>();
public List<OrderDescription> Orders
{
get { return new List<OrderDescription>(_orders); }
private set { _orders = value; }
}
public int ProductCount
{
get
{
int itemCount = 0;
this.Orders.ForEach(o => itemCount += o.Products.Count);
return itemCount;
}
}
public int CustomerCount
{
get
{
//since there's only one customer per order, just return the number of orders
return this.Orders.Count;
}
}
public void SubmitOrderRequest(OrderSpecification orderSpec)
{
if (orderSpec.IsValid)
{
List<OrderDescription> orders = this.Orders;
//Since the particular customer may already have an order, we might as well add to an existing
OrderDescription existingOrder = orders.FirstOrDefault(o => string.Compare(orderSpec.Order.Customer.Name, o.Customer.Name, true) == 0) as OrderDescription;
if (existingOrder != null)
{
List<Product> existingProducts = orderSpec.Order.Products;
orderSpec.Order.Products.ForEach(p => existingOrder.AddProduct(p));
}
else
{
orders.Add(orderSpec.Order);
}
this.Orders = orders;
}
else
orderSpec.Validate(); //Let the OrderSpecification pass the business logic validation down the chain
}
}
public enum IndustryCategory
{
Residential,
Textile,
FoodServices,
Something
}
public class OrderSpecification : IValidationInfo
{
public OrderDescription Order { get; private set; }
public OrderSpecification(string customerName, Product product)
{
//Should use a method in the class to search and retrieve Customer... pretending here
CustomerDescription customer = null;
switch (customerName)
{
case "Customer1":
customer = new CustomerDescription() { Name = customerName, Category = IndustryCategory.FoodServices };
break;
case "Customer2":
customer = new CustomerDescription() { Name = customerName, Category = IndustryCategory.Residential };
break;
case "Customer3":
customer = new CustomerDescription() { Name = customerName, Category = IndustryCategory.Textile };
break;
}
//Create an OrderDescription to potentially represent the order... valid or not since this is
//a specification being used to request the order
this.Order = new OrderDescription(new List<Product>() { product }, customer);
}
#region IValidationInfo Members
private readonly string[] _validationItems =
{
"OrderDescription"
};
public string[] ValidationItems
{
get { return _validationItems; }
}
public bool IsValid
{
get
{
List<string> validationErrors = GetValidationErrors();
if (validationErrors != null && validationErrors.Count > 0)
return false;
else
return true;
}
}
public void Validate()
{
List<string> errorMessages = GetValidationErrors();
if (errorMessages != null && errorMessages.Count > 0)
{
StringBuilder errorMessageReported = new StringBuilder();
errorMessages.ForEach(em => errorMessageReported.AppendLine(em));
throw new Exception(errorMessageReported.ToString());
}
}
public List<string> GetValidationErrors()
{
List<string> errorMessages = new List<string>();
foreach (string item in this.ValidationItems)
{
string errorMessage = GetValidationError(item);
if (!string.IsNullOrEmpty(errorMessage))
errorMessages.Add(errorMessage);
}
return errorMessages;
}
public string GetValidationError(string itemName)
{
switch (itemName)
{
case "OrderDescription":
return ValidateOrderDescription();
default:
return "Invalid item name.";
}
}
#endregion
private string ValidateOrderDescription()
{
string errorMessage = string.Empty;
if (this.Order == null)
errorMessage = "Order was not instantiated.";
else
{
if (!this.Order.IsValid)
{
List<string> orderErrors = this.Order.GetValidationErrors();
orderErrors.ForEach(ce => errorMessage += "\n" + ce);
}
}
return errorMessage;
}
}
public class CustomerDescription : IValidationInfo
{
public string Name { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public int ZipCode { get; set; }
public IndustryCategory Category { get; set; }
#region IValidationInfo Members
private readonly string[] _validationItems =
{
"Name",
"Street",
"City",
"State",
"ZipCode",
"Category"
};
public string[] ValidationItems
{
get { return _validationItems; }
}
public bool IsValid
{
get
{
List<string> validationErrors = GetValidationErrors();
if (validationErrors != null && validationErrors.Count > 0)
return false;
else
return true;
}
}
public void Validate()
{
List<string> errorMessages = GetValidationErrors();
if (errorMessages != null && errorMessages.Count > 0)
{
StringBuilder errorMessageReported = new StringBuilder();
errorMessages.ForEach(em => errorMessageReported.AppendLine(em));
throw new Exception(errorMessageReported.ToString());
}
}
public List<string> GetValidationErrors()
{
List<string> errorMessages = new List<string>();
foreach (string item in this.ValidationItems)
{
string errorMessage = GetValidationError(item);
if (!string.IsNullOrEmpty(errorMessage))
errorMessages.Add(errorMessage);
}
return errorMessages;
}
public string GetValidationError(string itemName)
{
//Validation methods should be called here... pretending nothings wrong for sake of discussion & simplicity
switch (itemName)
{
case "Name":
return string.Empty;
case "Street":
return string.Empty;
case "City":
return string.Empty;
case "State":
return string.Empty;
case "ZipCode":
return string.Empty;
case "Category":
return string.Empty;
default:
return "Invalid item name.";
}
}
#endregion
}
public class Product
{
public IndustryCategory Category { get; private set; }
public string Description { get; private set; }
public Product(IndustryCategory category, string description)
{
this.Category = category;
this.Description = description;
}
}
public class OrderDescription : IValidationInfo
{
public CustomerDescription Customer { get; private set; }
private List<Product> _products = new List<Product>();
public List<Product> Products
{
get { return new List<Product>(_products); }
private set { _products = value; }
}
public OrderDescription(List<Product> products, CustomerDescription customer)
{
this.Products = products;
this.Customer = customer;
}
public void PlaceOrder()
{
//If order valid, place
if (this.IsValid)
{
//Do stuff to place order
}
else
Validate(); //cause the exceptions to be raised with the validate because business rules were broken
}
public void AddProduct(Product product)
{
List<Product> productsToEvaluate = this.Products;
//some special read, validation, quantity check, pre-existing, etc here
// doing other stuff...
productsToEvaluate.Add(product);
this.Products = productsToEvaluate;
}
#region IValidationInfo Members
private readonly string[] _validationItems =
{
"Customer",
"Products"
};
public string[] ValidationItems
{
get { return _validationItems; }
}
public bool IsValid
{
get
{
List<string> validationErrors = GetValidationErrors();
if (validationErrors != null && validationErrors.Count > 0)
return false;
else
return true;
}
}
public void Validate()
{
List<string> errorMessages = GetValidationErrors();
if (errorMessages != null && errorMessages.Count > 0)
{
StringBuilder errorMessageReported = new StringBuilder();
errorMessages.ForEach(em => errorMessageReported.AppendLine(em));
throw new Exception(errorMessageReported.ToString());
}
}
public List<string> GetValidationErrors()
{
List<string> errorMessages = new List<string>();
foreach (string item in this.ValidationItems)
{
string errorMessage = GetValidationError(item);
if (!string.IsNullOrEmpty(errorMessage))
errorMessages.Add(errorMessage);
}
return errorMessages;
}
public string GetValidationError(string itemName)
{
switch (itemName)
{
case "Customer":
return ValidateCustomer();
case "Products":
return ValidateProducts();
default:
return "Invalid item name.";
}
}
#endregion
#region Validation Methods
private string ValidateCustomer()
{
string errorMessage = string.Empty;
if (this.Customer == null)
errorMessage = "CustomerDescription is missing a valid value.";
else
{
if (!this.Customer.IsValid)
{
List<string> customerErrors = this.Customer.GetValidationErrors();
customerErrors.ForEach(ce => errorMessage += "\n" + ce);
}
}
return errorMessage;
}
private string ValidateProducts()
{
string errorMessage = string.Empty;
if (this.Products == null || this.Products.Count <= 0)
errorMessage = "Invalid Order. Missing Products.";
else
{
foreach (Product product in this.Products)
{
if (product.Category != Customer.Category)
{
errorMessage += string.Format("\nThe Product, {0}, category does not match the required Customer category for {1}", product.Description, Customer.Name);
}
}
}
return errorMessage;
}
#endregion
}
Any reason you wouldn't want the constructor to noisily throw an exception if the information is valid? It's best to avoid ever creating an object in an invalid state, in my experience.
It's completely depends on the client. There's a trade-off as you already mentioned. By default approach number 1 is my favorite. Creating smart classes with good encapsulation and hiding details from client. The level of smartness depends who is going to use the object. If client is business aware you can reveal details according to the level of this awareness. This is a dichotomy and should not be treated as black or white.
Well if I correctly understood, there are basically two question - whether you should fail right away or later and whether you should omit/assume certain information.
1) I always prefer failing as soon as possible - good example is failing at compile time vs failing at run time - you always want to fail at compile time. So if something is wrong with the state of some object, as Jon said - throw exception right away as loudly as you can and deal with it - do not introduce additional complexity down the road as you'll be heading for if/elseif/elseif/elseif/else mumbo jumbo.
2) When it comes to user input, if you are in position to simply filter out errors automatically - just do it. For example, I almost never ask users for country - if I really need it, I automatically detect it from IP and display it in the form. It's way easier if user just needs to confirm/change the data - and I don't need to deal with null situation.
Now, in case we are talking about the data generated by code during some processing - for me situation is drastically different - I always want to know an much as possible (for easier debugging down the road) and ideally you never should destroy any piece of information.
To wrap up, in your case I would recommend that you keep IsValid as simple yes/no (not yes/no/maybe/kindaok/etc). If you can fix some problems automatically - do it, but consider that they keep object in IsValid yes. For everything else, you throw exception and go to IsValid=no.
I'm maintaining a legacy WebForms application and one of the pages just serves GET requests and works with many query string parameters. This work is done in the code-behind and does a lot of this type of check and casting.
protected override void OnLoad(EventArgs e)
{
string error = string.Empty;
string stringParam = Request.Params["stringParam"];
if (!String.IsNullOrEmpty(stringParam))
{
error = "No parameter";
goto LoadError;
}
Guid? someId = null;
try
{
someId = new Guid(Request.Params["guidParam"]);
}
catch (Exception){}
if (!someId.HasValue)
{
error = "No valid id";
goto LoadError;
}
// parameter checks continue on
LoadError:
log.ErrorFormat("Error loading page: {0}", error);
// display error page
}
I'd like to create a testable class that encapsulates this parsing and validation and moves it out of the code-behind. Can anyone recommend some approaches to this and/or examples?
As a first big step, I'd probably create some form of mapper/translator object, like this:
class SpecificPageRequestMapper
{
public SpecificPageRequest Map(NameValueCollection parameters)
{
var request = new SpecificPageRequest();
string stringParam = parameters["stringParam"];
if (String.IsNullOrEmpty(stringParam))
{
throw new SpecificPageRequestMappingException("No parameter");
}
request.StringParam = stringParam;
// more parameters
...
return request;
}
}
class SpecificPageRequest
{
public string StringParam { get; set; }
// more parameters...
}
Then your OnLoad could look like this:
protected override void OnLoad(EventArgs e)
{
try
{
var requestObject = requestMapper.Map(Request.Params);
stringParam = requestObject.StringParam;
// so on, so forth. Unpack them to the class variables first.
// Eventually, just use the request object everywhere, maybe.
}
catch(SpecificPageRequestMappingException ex)
{
log.ErrorFormat("Error loading page: {0}", ex.Message);
// display error page
}
}
I've omitted the code for the specific exception I created, and assumed you instantiate a mapper somewhere in the page behind.
Testing this new object should be trivial; you set the parameter on the collection passed into Map, then assert that the correct parameter on the request object has the value you expect. You can even test the log messages by checking that it throws exceptions in the right cases.
Assuming that you may have many such pages using such parameter parsing, first create a simple static class having extension methods on NamedValueCollection. For example,
static class Parser
{
public static int? ParseInt(this NamedValueCollection params, string name)
{
var textVal = params[name];
int result = 0;
if (string.IsNullOrEmpty(textVal) || !int.TryParse(textVal, out result))
{
return null;
}
return result;
}
public static bool TryParseInt(this NamedValueCollection params, string name, out int result)
{
result = 0;
var textVal = params[name];
if (string.IsNullOrEmpty(textVal))
return false;
return int.TryParse(textVal, out result);
}
// ...
}
Use it as follows
int someId = -1;
if (!Request.Params.TryParseInt("SomeId", out someId))
{
// error
}
Next step would be writing page specific parser class. For example,
public class MyPageParser
{
public int? SomeId { get; private set; }
/// ...
public IEnumerable<string> Parse(NamedValueCollection params)
{
var errors = new List<string>();
int someId = -1;
if (!params.TryParseInt("SomeId", out someId))
{
errors.Add("Some id not present");
this.SomeId = null;
}
this.SomeId = someId;
// ...
}
}