I am using Sharp Architechture and Rhino Mocks with NUnit.
I have a test service that looks like this
public class TestService : ITestService {
public TestService(ITestQueries testQueries, IRepository<Test> testRepository,
IApplicationCachedListService applicationCachedListService) {
Check.Require(testQueries != null, "testQueries may not be null");
Check.Require(applicationCachedListService != null, "applicationCachedListService may not be null");
_testQueries = testQueries;
_testRepository = testRepository;
_applicationCachedListService = applicationCachedListService;
}
I then have this method in my service
public string Create(TestFormViewModel viewModel, ViewDataDictionary viewData, TempDataDictionary tempData) {
if (!viewData.ModelState.IsValid) {
tempData.SafeAdd(viewModel);
return "Create";
}
try {
var test = new Test();
UpdateFromViewModel(test, viewModel);
_testRepository.SaveOrUpdate(test);
tempData[ControllerEnums.GlobalViewDataProperty.PageMessage.ToString()]
= string.Format("Successfully created product '{0}'", test.TestName);
}
catch (Exception ex) {
_testRepository.DbContext.RollbackTransaction();
tempData[ControllerEnums.GlobalViewDataProperty.PageMessage.ToString()]
= string.Format("An error occurred creating the product: {0}", ex.Message);
return "Create";
}
return "Index";
}
}
I then have a Controller that looks like this:
[ValidateAntiForgeryToken]
[Transaction]
[AcceptVerbs(HttpVerbs.Post)]
[ModelStateToTempData]
public ActionResult Create(TestFormViewModel viewModel) {
return RedirectToAction(_testService.Create(viewModel, ViewData, TempData));
}
I want to write a simple test to see if when !viewData.ModelState.IsValid I return "Create".
I have this so far but am confused because it really is not testing the controller it is just doing what I am telling it to do in the return.
[Test]
public void CreateResult_RedirectsToActionCreate_WhenModelStateIsInvalid(){
// Arrange
var viewModel = new TestFormViewModel();
_controller.ViewData.ModelState.Clear();
_controller.ModelState.AddModelError("Name", "Please enter a name");
_testService.Stub(a => a.Create(viewModel, new ViewDataDictionary(), new TempDataDictionary())).IgnoreArguments().Return("Create");
// Act
var result = _controller.Create(viewModel);
// Assert
result.AssertActionRedirect().ToAction("Create"); //this is really not testing the controller??.
}
Any help is appreciated.
It looks like you try to write not Unit tests. It's more like Integration test. Following Unit-testing ideology you have two Units: Service and Controller. The idea is that you should test each unit separately and keep your tests simple. According this first of all you should write tests for TestService. After that, when you cover it, write tests for your Controller using Stubs/Mocks for TestService. So your test for Controller looks right, it tests that redirect is happened according to result from Service.Create method. You should add tests for your TestService without controller context and it's got a good coverage.
If you want to test this units together then you must not use mocks and it will be more like Integration tests.
Additionally to cover integration between modules you can write web based tests using some tools like WatiN or Selenium for testing whole application.
But in any case write unit tests for separate parts is a good practice.
Related
I have a .NetCore MVC project and I'm trying to unit test my controller.
ViewModel (Note the [Required] attribute):
public class Bank : BaseObject
{
[Required]
[DisplayName("Bank")]
public string Name { get; set; }
}
Controller Action:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Name")] Bank bank)
{
if (ModelState.IsValid)
{
await _bankService.Insert(bank);
return RedirectToAction(nameof(Index));
}
return View(bank);
}
Unit Test:
[Test]
public async Task When_PostingCreateBankThatIsInvalid_ThenBankIsReturned()
{
var bank = new Bank
{
//nothing set = invalid state - this is what we want
};
var controller = new BanksController(null);
var response = await controller.Create(bank);
}
I was expected the line if (ModelState.IsValid) to return false as Name is required - then I was going to perform my Asserts based on that. But the result is true and so we try to insert a bank.
What am I doing wrong here? I've Googled but I can only find answers that don't relate to unit tests. I thought the ModelState upheld the [Required] attribute?
If I test using the UI I am unable to create a bank without a name - it never even reaches the controller (as expected).
According to the docs:
Model validation occurs prior to each controller action being invoked
so I believe the problem lies with how I'm creating the BanksController. Am I approaching this test in the wrong manner? I wonder if I should just be setting the ModelState to invalid in the test...?
Attributes are metadata that is only recognized by the framework at run time and not during a unit test as they are actually read by the model binder that when the application is running.
For the state to change you will either have to run an integration test where the necessary parts of the framework are available to update the model state,
or update the model state manually since model binding isn't running (though an integration test would be used to exercise model binding) in the controller so that the test behaves as expected when being exercised.
[Test]
public async Task When_PostingCreateBankThatIsInvalid_ThenBankIsReturned() {
//Arrange
var bank = new Bank
{
//nothing set = invalid state - this is what we want
};
var controller = new BanksController(null);
controller.ModelState.AddModelError("Name","Name required");
//Act
var response = await controller.Create(bank);
//Assert
response.Should().NotBeNull()
.And.BeOfType<ViewResult>();
var viewResult = response as ViewResult;
viewResult.Model.Should().Be(model);
}
Don't try to test model validation or model binding in your unit tests - just test your action method's behavior when confronted with a particular ModelState value.
Reference Test controller logic in ASP.NET Core
Even though there are couple of Posts on StackOverflow about Unit Testing Action Result in MVC, I have a specific Question ....
Here is my ActionResult in Controller:
public ActionResult Index()
{
return View(db.Products.ToList());
}
Every Item in Products has different attributes like Name,Photo,Quantity etc..
I wrote a testmethod for this method .It looks as follows :
private CartEntity db = new CartEntity();
[TestMethod]
public void Test_Index()
{
//Arrange
ProductsController prodController = new ProductsController();
ViewResult = prodController.Index();
}
What Should I compare in this case since there are no parameters are being passed into Index Action
Check out the ViewResult class, this can show you what else you could test.
What you need to do is mock your DbContext and supply it with data in the Products property (DbSet<>) as this is being called in your controller's action.
You can then test
The type being returned
The model on the ViewResult
The ViewName which should be empty or Index
Sample code
[TestMethod]
public void Test_Index()
{
//Arrange
ProductsController prodController = new ProductsController(); // you should mock your DbContext and pass that in
// Act
var result = prodController.Index() as ViewResult;
// Assert
Assert.IsNotNull(result);
Assert.IsNotNull(result.Model); // add additional checks on the Model
Assert.IsTrue(string.IsNullOrEmpty(result.ViewName) || result.ViewName == "Index");
}
If you need help mocking a DbContext there are existing frameworks and articles on this subject. Here is one from Microsoft titled Testing with a mocking framework. Ideally you should be injecting your dependencies (including DbContext instances) into the constructors of your Controller instances using a DI framework like AutoFac or Unity or NInject (the list goes on). This also makes unit testing much easier.
I created an ASP.NET MVC 5 application, with the following model class using a globalized Required validation attribute on its Email property:
public class Person
{
[Required(ErrorMessageResourceType = typeof(Resources),
ErrorMessageResourceName = "EmptyEmailError")]
public string Email { get; set; }
}
The Create POST action method of the PersonController class is:
[HttpPost]
public ActionResult Create(FormCollection formData)
{
Person newPerson = new Person();
UpdateModel(newPerson, formData);
if (ModelState.IsValid) {
// code to create the new person
return View("Index", newPerson);
}
else {
return View();
}
}
And the test case for verifying that a person with an empty e-mail address is not created is:
[TestMethod]
void Create_DoesNotCreatePerson_WhenEmailIsEmpty()
{
// arrange
FormCollection person = new FormCollection() { { "Email", string.Empty } };
PersonController controller = new PersonController();
controller.ControllerContext = new ControllerContext();
// act
controller.Create(person);
// assert
Assert.IsFalse(controller.ModelState.IsValid);
}
With this code the normal execution works fine. However, the test doesn't pass, which means that UpdateModel is returning true and thus not validating the person model correctly. The TryUpdateModelmethod doesn't work too.
But if I remove the ErrorMessage arguments from the Required validation attribute in the Person class (i.e. leaving it just as [Required]), then the test passes.
So I don't know why UpdateModel doesn't validate the globalized Person model when I call the Create action method from the test case.
I'm happy to see you are writing unit tests for your code :)
First, why don't you just pass Person in the first place, do you really need FormCollection?
[TestMethod]
void Create_DoesNotCreatePerson_WhenEmailIsEmpty()
{
// arrange
FormCollection person = new FormCollection() { { "Email", string.Empty } };
PersonController controller = new PersonController();
controller.ControllerContext = new ControllerContext();
// third, you might need to add this (haven't tested this)
controller.ModelState.AddModelError("Email", "fakeError");
// act
controller.Create(person);
// assert
Assert.IsFalse(controller.ModelState.IsValid);
}
Fourth and most important. I dont' think you should test like this. You want to test the outcome for different scenarios. That means you want to test the return value for the method. When you say Assert.IsFalse(controller.ModelState.IsValid); you basically say you don't trust Microsoft's code and you want to test their code. I can assure you they have unit tests on their code and you do not need to test it again ;)
Instead, test what view is returned and what object is returned from the action result. Here is a complete article for how to unit test this -> https://msdn.microsoft.com/en-us/library/gg416511(VS.98).aspx (See "Creating a test for retrieving contacts"). Hope this helps!
EDIT (Answer to comments):
Sory for the long response time.
I removed to "second".
If you want to to test the real production code, you want to do integration tests, not unit tests. If unit tests is what you want I recommend you to follow my solution proposal, otherwise create integration tests. I don't know how to convince you more then I've tried so read more and from several sources and I think you will agree later on how to do unit testing in this case :)
Good luck!
Below is the code of my method :-
[HttpPost]
public ActionResult Index(productViewModel model)
{
if (model != null)
{
return PartialView("_ProductGrid", GetProduct(model));
}
else
{
return RedirectToAction("Index", "Product");
}
}
And Below is code for unit testing method (in C#,MVC) :-
[TestMethod]
public void Index_WithModel_PostTest()
{
//Arrange
ProductController controller = new ProductController();
var model = new productViewModel()
{
Name="product1",
Description="desc"
};
//Act
PartialViewResult actual = controller.Index(model) as PartialViewResult;
if (actual != null)
{
var viewmodel = (productViewModel)((ViewResultBase)(actual)).Model;
int matches = _productService.GetDeals("", model.Description).Count +
_productService.GetInsurance("", model.Description).Count +
_productService.GetCategory("", model.Description).Count;
//Assert
Assert.IsNotNull(actual);
Assert.IsInstanceOfType(actual, typeof(PartialViewResult));
Assert.IsInstanceOfType(viewmodel, typeof(productViewModel));
Assert.AreEqual("_ProductGrid", actual.ViewName);
Assert.AreEqual(matches, viewmodel.Products.Count());
}
}
you can see on the below part of the method is that i am fetching the Products from all of the 3 methods. But all of those 3 methods are the ProductService dependency.And i want to know that should I mock that condition ? Or i can do in some other way ? I want to Assert the count of matches variable and the actual.Product.Count.
i think the essence of unit testing is to take advantage of separation of concerns. U should use moq to create a mock data accessed by a created repository and call that repository in the controller and then pass it to the unit testing.the essence of separation of concerns and unit testing is to be able to test each layer separately.Browse on MOQ
In my unit tests, I find that when I return from a controller action using View() with no view name, ViewResult.ViewName is set to string.Empty. In order for this to be set, it has to be specified as a parameter to the View() call. For example, given the following unit test:
[TextFixture]
public class MyControllerTests
{
[Test]
public void TestMyAction()
{
var controller = new MyController();
var result = controller.MyAction();
Assert.AreEqual("MyAction", result.ViewName);
}
}
The following action implementation will cause the unit test to fail:
public class MyController : Controller
{
public ActionResult MyAction()
{
return View();
}
}
whilst this one will pass:
public class MyController : Controller
{
public ActionResult MyAction()
{
return View("MyAction");
}
}
I'm using ASP.NET MVC 2 (pre-beta) on .NET 4.0. I'm not using anything .NET 4.0-specific, however. I find this behaviour odd because I had thought that the ViewName was one of the reliable properties that could be checked in the unit tests' assertions.
This is a well known "feature" of ASP.NET MVC. Microsoft has documented it since the first version...
When no explicit view name is specified, the framework is trying to find one based on conventions (in "Views\controllername\actionname" or "Shared\controllername\actionname"). ViewName is only relevant if you want to deviate from that convention. So your unit test makes false assumptions.
HTH.