How to define a Controller context when testing an ActionResult - c#

I have an ActionResult which works consistently in an MVC 5 project with EntityFramework and Epplus. Clicking an Action link on the View triggers this ActionResult, which sends the selected model to a fresh Excel document.
I am learning to do unit testing my MVC code in Visual Studio 2013 (using the Nuget Xunit package) and figured I'd start small, by doing the equivalent of a hello world test on the ActionResult by asserting that the ActionResult is not null.
The test failed with this response: "System.InvalidOperationException : No connection string named 'StudentContext' could be found in the application config file."
I understand what the error message means, but my question is how I properly define the Controller context in a Testing project. Am I missing something simple and obvious, like defining a context variable, or am I going about this completely the wrong way?
This is the block of code I am using to test my ActionResult.
using StudentProject.Controllers;
using System.Web.Mvc;
using Xunit;
namespace StudentProject.Tests.Controllers
{
public class StudentRosterControllerTest
{
[Fact]
public void ExportToExcel_IsNotNull()
{
// Arrange
StudentRostersController controller = new StudentRostersController();
ActionResult ExcelExport;
// Act
ExcelExport = controller.ExportToExcel();
// Assert
Assert.NotNull(ExcelExport);
}
}
}
This is the code I am testing. It is is an auto-scaffolded controller, with the auto-generated crud methods hidden and the single method to be tested shown.
using OfficeOpenXml;
using StudentProject.Models;
using System.Collections;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Net;
using System.Web.Mvc;
namespace StudentProject.Controllers
{
public class StudentRostersController : Controller
{
private StudentContext db = new StudentContext();
// Auto-scaffolded CRUD methods not shown
// This ActionResult exports the StudentRoster model
// to a fresh Excel file.
public ActionResult ExportToExcel()
{
IEnumerable<StudentRoster> query = db.StudentRosters.AsEnumerable();
using (var excelFile = new ExcelPackage())
{
ExcelWorksheet worksheet =
excelFile.Workbook.Worksheets.Add("Sheet1");
worksheet.Cells["A1"].LoadFromCollection(Collection: query,
PrintHeaders: true);
// Results in file downloaded to user's default
// "My Downloads" folder.
return File(excelFile.GetAsByteArray(),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"Export.xlsx");
}
}
}
}

System.InvalidOperationException : No connection string named
'StudentContext' could be found in the application config file."
make sure the app.config file for the Test project has the proper connection string settings for your EF.
Test project app.config
<connectionStrings>
<add name="StudentContext" connectionString="..." providerName="System.Data.EntityClient" />
</connectionStrings>
how I properly define the Controller context in a Testing project
Your test should try to replicate the run time environment. Provided the minimum needed to test the SUT.
namespace StudentProject.Tests.Controllers
{
public class StudentRosterControllerTest
{
[Fact]
public void ExportToExcel_IsNotNull()
{
// Arrange
StudentRostersController controller = new StudentRostersController();
controller.ControllerContext = new ControllerContext() {
Controller = controller,
//...other properties needed for test
};
// Act
var actionResult = controller.ExportToExcel();
// Assert
Assert.NotNull(actionResult);
}
}
}
Given that you are connecting to your actual database then this would be considered more of an integration test.
I would suggest abstracting your data access so that you can mock it in unit tests.

Related

Visual Studio 2022 not running XUnit tests

I've created a EntityFramework ASP.NET solution and i'm trying to create a XUnit test project to test my differents classes i've created.
I've created a TestClass for my Activity Class :
using LADS_Model;
using LADS_WebUI.Controllers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
using Xunit;
using Assert = Microsoft.VisualStudio.TestTools.UnitTesting.Assert;
namespace LADS_XUnit
{
public class UnitTest_Activity
{
[TestClass]
public class ActivityController
{
private List<Activity> GetTestActivities()
{
var testActivities = new List<Activity>();
testActivities.Add(new Activity { Id = 1, Name = "Chaussure" });
testActivities.Add(new Activity { Id = 2, Name = "Crevettes" });
testActivities.Add(new Activity { Id = 3, Name = "Sandwich" });
return testActivities;
}
[TestMethod]
public void GetAllActivities_ShouldReturnAllActivities()
{
var testActivities = GetTestActivities();
var controller = new ActivityController();
var result = controller.GetTestActivities();
Assert.Equals(testActivities.Count, result.Count);
}
}
}
}
The problem is that when I launch my testClass, I do have the Test showing up in the test Explorer but VS tells me that the test did not execute and I have no idea why because it's not showing any errors or any messages to explain why it didnt execute
Output of Tests :
You're using the wrong method attributes for xUnit, so the xUnit Runner won't discover your tests:
You're using [TestClass] and [TestMethod], but xUnit uses [Fact] (and [Theory], and others) only on methods, and doesn't use any attributes on test class` types.
xUnit's website has a table that matches and compares NUnit's, xUnit's, and MSTest's attributes.
So remove all [TestClass] attributes and change all [TestMethod] attributes to [Fact].
I see you have this:
using Assert = Microsoft.VisualStudio.TestTools.UnitTesting.Assert;
...so I assume you're porting existing MSTest code to xUnit, in which case rather than changing the attributes in your code-base, you could alias MSTest's attributes to xUnit:
using TestClassAttribute = SomeDummyAttribute;
using TestMethodAttribute = Xunit.FactAttribute;
internal sealed class SomeDummyAttribute : Attribute {}
If you're using C# 10.0 or later you can use global using which will be shared by all source-files in the same project.
Also, consider using Shouldly or Fluent Assertions instead of the Assert/Asserts classes.

Trying to connect to a database and present view with Entity Framework. Getting Error with dbContext in controller

I am trying to view a database in my browser which I have connected successfully through entity framework to my MVC project.
However I keep getting an error under the using statement applying to dbContext in my controller when trying to preform some executions.
This db.context from what I heard is supposed to be created at the time of model creation when I connected to the database in the first place. Even in the comments of my code which came from somewhere else, it says MyDatabaseEntities is dbContext, which is created at time of model creation. For some reason I tried using my Context which is boringly called Model1 and it isn't recognizing it.
Does anyone know why this is? Can anyone please help me correct this?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace WebApplication6.Controllers
{
public class UserController : Controller
{
//
// GET: /User/
public ActionResult Index()
{
return View();
}
public ActionResult GetUser()
{
return View();
}
public JsonResult GetAllUser()
{
List<Location> allUser = new List<Location>();
// Here "MyDatabaseEntities " is dbContext, which is created at time of model creation.
using (Model1 dc = new Model1())
{
allUser = dc.UserMasters.ToList();
}
return new JsonResult { Data=allUser, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
}
public JsonResult GetUserWithParameter(string prefix)
{
List<Location> allUser = new List<Location>();
// Here "MyDatabaseEntities " is dbContext, which is created at time of model creation.
using (Model1 dc = new Model1())
{
allUser = dc.UserMasters.Where(a => a.Username.Contains(prefix)).ToList();
}
return new JsonResult { Data = allUser, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
}
}
}
If Model1 has a red squiggly, that's because it either doesn't exist in any project reference and/or the namespace has not been included in the current file.
If you right-click on Model1 in your code, you should see a Resolve item in the context menu that appears. If you do, then simply use on of the options there to resolve the reference. If you do not see the Resolve item, then that means it does not exist in any of the project references. You either need to add a new project reference or figure out why it otherwise doesn't exist. For example, sometimes if you a referencing something from a class library in another project, if the class library is failing to build, the other project will not be able to find the reference even though it has a reference to that class library.
All that said, there's certain things here that are going to cause you issues. First, you should pretty much never use using with your context. It's almost always a recipe for disaster as there's things like lazy-loading code that lives on long after the context has been disposed, and basically become time bombs in your code. Instead, you should create a private/protected field on your controller or (better) use dependency injection to inject the context into your controller.
Field
public class FooController : Controller
{
private readonly ApplicationDbContext db = new ApplicationDbContext();
Dependency Injection
public class FooController : Controller
{
private readonly DbContext db;
public class FooController(DbContext db)
{
this.db = db;
}
(combined with configuration code for your DI container of choice, of course)
Either way, your context will be live throughout the life of the request, and you won't generate runtime errors from something trying to hit the database after the context has been disposed.
Second, you should really stay away from EDMX. It's deprecated, and even if it wasn't, it's just brittle and frankly a pain in the posterior. You should just use POCOs and a context, the approach referred to misleadingly as "Code First". However, despite the name, Code First can just as easily work with an existing database. There's frankly just no reason to ever use anything else. If you're working with an existing database, I have a post detailing how to set that up.

ASP.NET 5 - MVC 6 - Unit Test a Controller that uses Url.Action

I have an MVC6 controller with the following line of code:
string link = Url.Action("Profile", "Account");
When I unit test this controller, that line fails with an error:
Value cannot be null.
Parameter name: helper
I don't really want to mock the Url.Action response plus I don't think I can because it's an extension method (static method). I want it to run and return like it would in a web environment.
What do I need to do when instantiating the controller in my unit test so that I the line above will execute as expected?
I see that I can do something like this in my unit test:
controller.Url = new UrlHelper(new ActionContextAccessor(), new DefaultActionSelector(...));
But I can't figure out what is needed to setup the ActionContextAccessor and/or the DefaultActionSelector (which requires more types that I'm not sure where to obtain or how to instantiate).
Has anyone already done this?
Thanks,
Kevin
My wife always tells me to just "walk away" from a problem when I'm stuck. And she's almost always right.
After realizing the solution above wasn't going to work (because it's MVC5), I took another look and realized that controller.Url is just an instance of IUrlHelper. I mocked that and poof the tests started to work. Here's the gist of what I'm doing. This made the test able to execute. I'm sure I can add some more to the mocking to actually verify it's functioning as expected.
Mock<IUrlHelper> urlHelperMock = new Mock<IUrlHelper>();
var controller = new BooksController();
controller.Url = urlHelperMock.Object;
Yeah, it was that easy LOL.
Thanks,
Kevin
Given:
namespace SampleWebApplication.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
string link = Url.Action("Profile", "Account");
return View();
}
}
}
This test seems to run through OK:
using System;
using System.Collections.Specialized;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SampleWebApplication.Controllers;
namespace SampleUnitTestProject
{
[TestClass]
public class HomeTests
{
private Mock<HttpRequestBase> RequestMock;
private Mock<HttpResponseBase> ResponseMock;
private Mock<HttpContextBase> ContextMock;
[TestInitialize]
public virtual void Setup()
{
this.RequestMock = new Mock<HttpRequestBase>();
this.ResponseMock = new Mock<HttpResponseBase>();
this.RequestMock.Setup(m => m.QueryString).Returns(new NameValueCollection());
this.RequestMock.Setup(m => m.Url).Returns(new Uri("http://www.somedomain.com"));
this.ContextMock = new Mock<HttpContextBase>();
this.ContextMock.Setup(m => m.Request).Returns(this.RequestMock.Object);
this.ContextMock.Setup(m => m.Response).Returns(this.ResponseMock.Object);
}
[TestMethod]
public void Test_Index()
{
// Arrange
using (var controller = new HomeController())
{
this.RequestMock.Setup(c => c.ApplicationPath).Returns("/tmp/testpath");
this.ResponseMock.Setup(c => c.ApplyAppPathModifier(It.IsAny<string>())).Returns("/mynewVirtualPath/");
var requestContext = new RequestContext(this.ContextMock.Object, new RouteData());
controller.Url = new UrlHelper(requestContext, new RouteCollection());
// Act
var result = controller.Index();
// Assert
}
}
}
}

Could not load type from another project in same assembly ASP.NET MVC

I have three asp.net mvc 5 projects in one solution. The solution is called Bank24 so the assembly too. Project Application_layer is main project. Also there is 2 more projects. There are BusinessLogic_layer where I'm working with database and Data layer where I'm creating database.
When I attempting to use class from BusinessLogic_layer in Application_layer I'm getting runtime server error -
Could not load type "BusinessLogic_layer.Services.DataService" from assembly "Bank24, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=null"
Project Application_layer already has reference to BusinessLogic_layer. Directory that contains needed class already linked with Using directive. So I don't know why it doesen't loading.
Here is code of Controller MainController. I need to use desired class in method Register. It is linked with using BusinessLogic_layer.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Microsoft.AspNet.Identity;
using Application_layer.Models;
using BusinessLogic_layer.Services;
namespace Application_layer.Controllers
{
[Authorize]
public class MainController : Controller
{
...
public UserManager<ApplicationUser> UserManager { get; private set; }
[HttpGet]
[AllowAnonymous]
//[ValidateAntiForgeryToken]
public void Register()
{
var user = new ApplicationUser() { UserName = "Admin" };
var result = UserManager.Create(user, "q1w2e3");
if (result.Succeeded)
{
DataService dataWorker = new DataService();
dataWorker.CreateUser("Admin", "Суренко Иван Васильевич", 0);
RedirectToAction("Login", "Authorization");
}
}
}
}
Here is code of DataService class which is in Services folder of another project
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using BusinessLogic_layer.Models;
using Data_layer.DataAccess;
using Data_layer.Models;
namespace BusinessLogic_layer.Services
{
public class DataService
{
...
public void CreateUser(string login, string name, byte role)
{
bool isEmployee = false;
if (role == 0)
isEmployee = true;
BankContext db = new BankContext();
db.Users.Add(new User() { Login = login, Name = name, Role = role, IsEmployee = isEmployee });
db.SaveChanges();
}
...
}
}
P.S. I have Visual Studio 2013
I've solved this problem. I started new project and begun testing. After a couple of hours I found several things. 1. Projects must be in different assemblys nevertheless they can be in one solution. I don't know why CLR could not load type when they are in same assembly. 2. It follows from 1 - all projects except main project must have empty project type.

What is wrong with my unit test code for this MVC project? I keep getting 'does not contain definition' and 'no extension method error'

(EDIT** I was able to get the code to compile and execute a unit test that passed. In addition to the code fixes, there was a problem with VS2010 running the unit test indefinitely. I had to replace a dll file that was changed during an aborted install of vs 2012. I posted the changes to the controller and unit test at the bottom of the page. Thanks to all who posted answers.)
This is the first question I've ever asked online about coding. I've been learning C# .NET and other associated stuff using free tutorials for about a year now. So far I've been able to research and troubleshoot everything on my own. I'm starting to venture into uncharted territory now and I can't seem to find an answer.
I have been working on a tutorial called "Learn MVC Model View Controller Step by Step in 7 days". Here is the link:
http://www.codeproject.com/Articles/259560/Learn-MVC-Model-view-controller-Step-by-Step-in-7
I have researched the suggested links for the error:
Error 'Mvccustomer.Models.Customer' does not contain a definition for 'DisplayCustomer' and no extension method 'DisplayCustomer' accepting a first argument of type 'Mvccustomer.Models.Customer' could be found (are you missing a using directive or an assembly reference?)
The problem I'm running into is that I can't seem to find a similar situation where someone is creating a unit test with a similar file reference. Mind you, I'm totally new to MVC and unit testing.
One problem with the tutorial is that that in the videos the author uses one set of namespaces/file names and another in the written tutorial. I was able to troubleshoot that problem on my own. For instance, in the beginning, he uses 'Mvccustomer' as a project name but by the 4th or 5th lab on the first day he's calling it 'Mvcinputscreen'.
I susspect that the trouble lies in how the customer class is referenced in the project but I can't figure it out so far.
Here is the unit test that gives me an error:
using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Mvccustomer.Models;
namespace MvcUnitTest
{
[TestClass]
public class UnitTest1
{
[TestMethod]
public void DisplayCustomer()
{
Customer obj = new Customer();
var varresult = obj.DisplayCustomer();
Assert.AreEqual("DisplayCustomer", varresult.ViewName);
}
}
}
Here is the customer class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Mvccustomer.Models;
namespace Mvccustomer.Models
{
public class Customer
{
public int Id { set; get; }
public string CustomerCode { set; get; }
public double Amount { set; get; }
}
}
This is the Display Customer View:
<%# Page Language="C#" Inherits="System.Web.Mvc.ViewPage<Mvccustomer.Models.Customer>" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>DisplayCustomer</title>
</head>
<body>
<div>
The customer id is <%= Model.Id %> <br />
The customer id is <%= Model.CustomerCode %> <br />
<%if (Model.Amount > 100)
{%>
This is a priveleged customer.
<% }
else
{ %>
This is a normal customer
<%} %>
</div>
</body>
</html>
And the customer controller:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Mvccustomer.Models;
namespace Mvccustomer.Controllers
{
public class CustomerController : Controller
{
//
// GET: /Customer/
public ActionResult Index()
{
return View();
}
public ActionResult FillCustomer()
{
return View();
}
public ActionResult DisplayCustomer(Customer obj)
{
return View(obj);
}
}
}
Let me know if I need to post any more elements of the project. When I build the Mvccustomer project it compiles fine with no errors. It's only the unit test that is giving me trouble. I imagine that this question is a bit convoluted and I eagerly await the learning experience that will come with all of the constructive criticism. Thank you.
Edited Controller and Unit Test that ultimately worked:
Customer Controller:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Mvccustomer.Models;
using Mvccustomer.Controllers;
namespace Mvccustomer.Controllers
{
public class CustomerController : Controller
{
//
// GET: /Customer/
public ActionResult Index()
{
return View();
}
public ActionResult FillCustomer()
{
return View();
}
[HttpPost]
public ActionResult DisplayCustomerView(CustomerModel customerModel)
{
var myView = View("DisplayCustomerView", customerModel);
//myView.ViewName = "DisplayCustomer";
return myView;
}
}
}
Edited Unit Test:
using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Mvccustomer.Models;
using Mvccustomer.Controllers;
using System.Web.Mvc;
namespace MvcUnitTest
{
[TestClass]
public class UnitTest1
{
[TestMethod]
public void DisplayCustomer()
{
// instantiates new instance of CustomerController class
CustomerController controller = new CustomerController();
var customer = new CustomerModel();
var customerViewActionResult = controller.DisplayCustomerView(customer);
var customerViewViewResult = customerViewActionResult as ViewResult;
Assert.AreEqual("DisplayCustomerView", customerViewViewResult.ViewName);
}
}
}
You are calling a method obj.DisplayCustomer() in a Customer object in the test. However, I don't see any DisplayCustomer method in Customer.
The CustomerController class has a method DisplayCustomer but this one requires one parameter of type Customer.
CustomerController obj = new CustomerController();
var varresult = obj.DisplayCustomer(new Customer());
If intellisense does not show the method, it's probably because the method does not exist or is private.
CustomerController obj = new CustomerController();
var result = obj.DisplayCustomer(new Customer()) **as ViewResult**;
Assert.AreEqual("Expected", result.ViewName);
You are testing for the view name that is supposed to be returned by the controller, but you didn't create a controller. Instead you are calling .DisplayCustomer() on the Customer not the CustomerController
I would also make your code a little more explanatory. Names of unit test functions should explain what they are testing. Variable names should explain what they are (obj is a bad name, because it is meaningless).
Consider reading another programmer's code and needing to understand how it works, or coming back to your own code 2 years from now and trying to remember how it worked. Giving things explanatory names helps. I would rewrite your test like this:
[TestMethod]
public void DisplayCustomer_ReturnsViewNamed_DisplayCustomer()
{
const string expectedViewName = "DisplayCustomer";
var customer = new Customer();
var controllerUnderTest = new CustomerController();
var result = controllerUnderTest.DisplayCustomer(customer);
Assert.AreEqual(expectedViewName, result.ViewName);
}
If you like reading through programming books, I highly recommend Clean Code by Robert Martin. It is written with code examples in Java, but Java is syntactically close to C#, and it is a great book on keeping your code readable, simple and well organized. I keep a cheat-sheet of notes from the book that I ofter refer to when coding.
Edit:
Regarding your new error:
'System.Web.Mvc.ActionResult' does not contain a definition for
'ViewName' and no extension method 'ViewName' accepting a first
argument of type 'System.Web.Mvc.ActionResult' could be found (are you
missing a using directive or an assembly reference?)
The method signature on your controller is:
public ActionResult DisplayCustomer(Customer obj)
It returns an ActionResult object. The property .ViewName does not exist on this type, it is actually a property of ViewResult.
The line inside the DisplayCustomer() method in the controller returns:
return View(obj);
The View() method actually returns a ViewResult, but the ViewResult class extends ActionResult:
public class ViewResult : ActionResult
So it is OK that your method signature is set to ActionResult but you actually always return a ViewResult since ViewResult is an ActionResult. (Typical OO inheritance stuff, but hopefully this part makes sense so far).
In your test, when you call DisplayCustomer() as far as the test knows all it has to go by is the method signature, which is tellign the compiler that it will return an ActionResult. Therefore the compiler is trying to find a property names ViewName on the Actionresult class which does not exist.
There are 2 ways you can fix this:
One is to simply cast the result in your test:
var varresult = (ViewResult)obj.DisplayCustomer();
Or, since that method always returns a ViewResult you can change the method signature to indicate that it returns this more specific type:
public ViewResult DisplayCustomer(Customer obj)
{
return View(obj);
}
I tried to boil this down to not using a mocking framework for simplicity, which would have eliminated or reduced the need for the try/catch (depending on your own preferences/style).
Just noticed one of the reasons you were having issues, the method DisplayCustomer() is supposed to return ViewResult not ActionResult.
However the tutorial still doesn't work, as getting the view name doesn't happen until ExecuteResult() is called.
[TestMethod]
public void DisplayCustomerTest_FindsCorrectViewName()
{
var expected = "DisplayCustomer";
var obj = new CustomerController();
var cContext = new ControllerContext();
cContext.RouteData.Values.Add("action", expected);
cContext.RouteData.Values.Add("controller", "Customer");
var actionResult = obj.DisplayCustomer(new Customer());
//not necessary but helpful
Assert.IsInstanceOfType(actionResult, typeof(ViewResult));
//down cast
var vResult = actionResult as ViewResult;
try // the view name is populated early, and we don't care about what else it does
{
vResult.ExecuteResult(cContext);
}
catch (NotImplementedException) {} //catch the most specific error type you can
Assert.AreEqual(expected, vResult.ViewName);
}
for more interesting ways to do this, or more advanced (matter of opinion, and appears to be missing any answer utilizing a proper mocking framework)
Get View Name where ViewResult.ViewName is empty string for Unit Testing
down cast explanation
pardon the styling, no Resharper or StyleCop installed at home.
for unit testing naming conventions consider Unit test naming best practices

Categories

Resources