NUnit skip save changes - asp.net

I'm testing a MVC ASP.Net Web Application and using NUnit to test its controllers. I test the Create method in the controllers and there's a step that it save changes to a stored local database. But NUnit always skips the save changes step. Is there anyway can solve it?

This is the method I coded in the controllers
[HttpPost]
public ActionResult Create([Bind(Include = "CourseId,CourseName,CoursCategoryId,Credit")] COURSE course)
{
try
{
if (ModelState.IsValid)
{
db.COURSEs.Add(course);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.CourseCategagoryId = new SelectList(db.CourseCategorys, "CourseCategoryId", "CourseCategoryId", course.CourseCategoryId);
return View(course);
}
catch(Exception e)
{
return RedirectToAction("Create", "COURSEs", new { id = 1 });
}
}
And this is the test method I used to test that method
[Test]
[TestCase("IT01", "Computer Science", "PRA", 4, "Index")]
public void AddCourseTest(string CourseId,
string CourseName, string CourseCategoryId, int Credit, string expected)
{
COURSE course = new COURSE
{
CourseCategoryId= CourseCategoryId,
CourseName= CourseName,
CourseCategoryId= CourseCategoryId,
Credit= Credit
};
COURSEsController = new COURSEsController ();
RedirectToRouteResult result = COURSEsController .Create(course ) as RedirectToRouteResult;
Assert.AreEqual(expected, result.RouteValues["action"].ToString());
}
As expected that the course I'll add hasn't existed in our database, so the result will redirect to "Index" page. But when I debug the test method, It seems that when it runs to the db.SaveChanges(), it threw exceptions and always return to "Create" page.

Related

Mocking Services in ASP.NET Core

I have a simple form to save and then use MailKit to provide email notification, with xUnit and Moq used for unit testing. I'm having difficulty setting up the unit test and associated services. I have a workaround ('if' statement in the action method) to only test the core repo saving functionality without also testing the email service. If I take out the if statement, the unit test does not have access to the appropriate methods, such as setting the web root path. The error is a null exception. If I default this value, there are other errors, such as "no database provider being configured for DbContext."
Is there a more appropriate way to set a unit test of this sort up? Or is it wrong to set up a unit test to test both the Create() and email functionality because it violates the one-function unit testing rule?
Unit test:
[Fact]
public void Can_Create_New_Lesson()
{
//Arrange
//create a mock repository
Mock<IHostingEnvironment> mockEnv = new Mock<IHostingEnvironment>();
Mock<ILessonRepository> mockRepo = new Mock<ILessonRepository>();
Mock<UserManager<AppUser>> mockUsrMgr = GetMockUserManager();
Mock<RoleManager<IdentityRole>> mockRoleMgr = GetMockRoleManager();
var opts = new DbContextOptions<AppIdentityDbContext>();
Mock <AppIdentityDbContext> mockCtx = new Mock<AppIdentityDbContext>(opts);
//create mock temporary data
Mock<ITempDataDictionary> tempData = new Mock<ITempDataDictionary>();
//create the controller
LessonController target = new LessonController(mockRepo.Object, mockEnv.Object, mockUsrMgr.Object, mockRoleMgr.Object, mockCtx.Object)
{
TempData = tempData.Object
};
//create a lesson
Lesson lesson = new Lesson { Title = "Unit Test", Domain= "Unit Test"};
//Act
//try to save the product using the Create method of the controller
IActionResult result = target.Create(lesson);
//Assert
//check that the repository was called
mockRepo.Verify(m => m.SaveLesson(lesson));
//check the result type is a redirection to the List action method of the controller
Assert.IsType<RedirectToActionResult>(result);
Assert.Equal("Success", (result as RedirectToActionResult).ActionName);
}
The Create() action method:
[HttpPost]
public IActionResult Create(Lesson lesson)
{
if (ModelState.IsValid)
{
repository.SaveLesson(lesson);
//This IF statement is a workaround for the unit test
//don't email users if the Title is "Unit Test"
if (lesson.Title != "Unit Test")
{
emailUsers(lesson);
}
TempData["message"] = $"{lesson.Title} has been saved";
//show the user that the update was made successfully
return RedirectToAction("Success");
}
else
{
//there is a problem with the data values
return View(lesson);
}
}
Email function:
public void emailUsers(Lesson lesson)
{
var webRoot = environment.WebRootPath;
var filePath = System.IO.Path.Combine(webRoot, "email\\NewLessonSubmitted.txt");
string message = System.IO.File.ReadAllText(filePath);
string domain = lesson.Domain;
IQueryable<AppUser> userList = GetUsersInRole(identityContext, domain);
//if there are users in that domain, send the email
if (userList != null)
{
foreach (AppUser user in userList)
{
sendEmail(domain, message, user.Email);
}
}
}
EDIT: I've instead implemented the email service as a class, as pointed out by MotoSV. However, I'm still getting an error for "No database provider has been configured for this DbContext" The stack trace for the exception points to the following method:
public static IQueryable<AppUser> GetUsersInRole(AppIdentityDbContext db, string roleName)
{
if (db != null && roleName != null)
{
var roles = db.Roles.Where(r => r.Name == roleName);
if (roles.Any())
{
var roleId = roles.First().Id;
return from user in db.Users
where user.Roles.Any(r => r.RoleId == roleId)
select user;
}
}
return null;
}
I have this constructor in my dbContext class:
public AppIdentityDbContext(DbContextOptions<AppIdentityDbContext> options)
: base(options) { }
EDIT: The solution (provided by MotoSV) was to:
1) Create an email service class with appropriate methods and
2) Install the appropriate Nuget package for Microsoft.EntityFrameworkCore.InMemory
3) mock the DbContext as:
var opts = new DbContextOptionsBuilder<AppIdentityDbContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
Mock<AppIdentityDbContext> mockCtx = new Mock<AppIdentityDbContext>(opts);
I would look at moving the code responsible for sending emails into it's own class. This class will implement an interface that can then be mocked in your test.
For example, create the interface and implementation:
public interface IEmailService
{
void SendEmail(string to, string from, string body);
}
public class EmailService : IEmailService
{
public void SendEmail(string to, string from string body)
{
...
}
}
The EmailService class will hold the functionality required to talk to MailKit. Then, register the IEmailService with .NET Core and add it to the constructor of your class:
public class LessonController : Controller
{
private readonly IEmailService _emailService;
public LessonController(IEmailService service, ...)
{
_emailService = emailService;
}
public void emailUsers(Lessong lesson)
{
...
if(userList != null)
{
foreach(...)
{
_emailService.Send(...);
}
}
...
}
}
In your test create a mock and pass that into your constructor.
First and foremost, you should never do stuff like putting in conditionals in your code for the purpose of unit testing. If for no other reason, you're violating the entire point of unit testing, as your test access different code paths than what your users actually experience; you learn nothing by doing this.
Testing that the repo actually saves is a job for a repo test not an action test. Likewise with your mail service: ensuring that an email is actually sent should be a test on your mail service, not your action method.
Long and short, your test here should simply ensure that the appropriate actions are taken (i.e. repo save is hit and email service send is hit). As such, you can drop in simple mocks that merely have those methods available to be hit. You don't need to (and shouldn't) be establishing full connections to the DB/SMTP server, as at that point you're integration testing, not unit testing.
Your applications send email class constructor should take an "email provider" object that is a generic email abstraction based on an IEmailProvider interface, and/or also take a IDataAccessProvider implementation.
Now you can mock both of these interfaces in the test and pass them to the send email class to test just your implementation.

How to unit-test transactions in Entity Framework Core?

I have a method that does some work in a transaction:
public async Task<int> AddAsync(Item item)
{
int result;
using (var transaction = await _context.Database.BeginTransactionAsync())
{
_context.Add(item);
// Save the item so it has an ItemId
result = await _context.SaveChangesAsync();
// perform some actions using that new item's ItemId
_otherRepository.Execute(item.ItemId);
transaction.Commit();
}
return result;
}
I'd like to add unit tests to check that if _context.SaveChangesAsync or _otherRepository.Execute fail then the transaction is rolled back, is that possible?
I can't see a way to do that using InMemory or SQLite?
#Ilya Chumakov's excellent answer allowed me to unit test for the transaction. Our discussion in the comments then exposed some interesting points that I thought were worth moving into an answer so they'd be more permanent and easier to see:
The primary point is that the events logged by Entity Framework change dependent on the database provider, which surprised me. If using the InMemory provider you get just one event:
Id:1; ExecutedCommand
Whereas if you use Sqlite for the in-memory database you get four events:
Id:1; ExecutedCommand
Id:5; BeginningTransaction
Id:1; ExecutedCommand
Id:6; CommittingTransaction
I hadn't expected the events logged to change depending on the DB provider.
To anyone wanting to look into this more, I captured the event details by changing Ilya's logging code as follows:
public class FakeLogger : ILogger
{
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception,
Func<TState, Exception, string> formatter)
{
var record = new LogRecord
{
EventId = eventId.Id,
RelationalEventId = (RelationalEventId) eventId.Id,
Description = formatter(state, exception)
};
Events.Add(record);
}
public List<LogRecord> Events { get; set; } = new List<LogRecord>();
public bool IsEnabled(LogLevel logLevel) => true;
public IDisposable BeginScope<TState>(TState state) => null;
}
public class LogRecord
{
public EventId EventId { get; set; }
public RelationalEventId RelationalEventId { get; set; }
public string Description { get; set; }
}
And then I adjusted my code that returns an in-memory database so that I could switch in-memory DB provider as follows:
public class InMemoryDatabase
{
public FakeLogger EfLogger { get; private set; }
public MyDbContext GetContextWithData(bool useSqlite = false)
{
EfLogger = new FakeLogger();
var factoryMock = Substitute.For<ILoggerFactory>();
factoryMock.CreateLogger(Arg.Any<string>()).Returns(EfLogger);
DbContextOptions<MyDbContext> options;
if (useSqlite)
{
// In-memory database only exists while the connection is open
var connection = new SqliteConnection("DataSource=:memory:");
connection.Open();
options = new DbContextOptionsBuilder<MyDbContext>()
.UseSqlite(connection)
.UseLoggerFactory(factoryMock)
.Options;
}
else
{
options = new DbContextOptionsBuilder<MyDbContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
// don't raise the error warning us that the in memory db doesn't support transactions
.ConfigureWarnings(x => x.Ignore(InMemoryEventId.TransactionIgnoredWarning))
.UseLoggerFactory(factoryMock)
.Options;
}
var ctx = new MyDbContext(options);
if (useSqlite)
{
ctx.Database.EnsureCreated();
}
// code to populate the context with test data
ctx.SaveChanges();
return ctx;
}
}
Finally, in my unit test I made sure to clear the event log just before the assert part of my test to ensure I don't get a false positive due to events that were logged during the arrange part of my test:
public async Task Commits_transaction()
{
using (var context = _inMemoryDatabase.GetContextWithData(useSqlite: true))
{
// Arrange
// code to set up date for test
// make sure none of our setup added the event we are testing for
_inMemoryDatabase.EfLogger.Events.Clear();
// Act
// Call the method that has the transaction;
// Assert
var result = _inMemoryDatabase.EfLogger.Events
.Any(x => x.EventId.Id == (int) RelationalEventId.CommittingTransaction);
You could check EF Core logs for a RelationalEventId.RollingbackTransaction event type. I provided full details here:
How to trace an Entity Framework Core event for integration testing?
How it could look:
Assert.True(eventList.Contains((int)RelationalEventId.CommittingTransaction));
I think you are asking about how to rollback when a commit fails, EF core will auto rollback on if any of the statement failed
Read more here
, if you are asking for other reason or you want to do something when rollback happens, just to add try catch blocks,
using (var transaction = await
_context.Database.BeginTransactionAsync()){
try {
_context.Add(item);
// Save the item so it has an ItemId
result = await _context.SaveChangesAsync();
// perform some actions using that new item's ItemId
_otherRepository.Execute(item.ItemId);
transaction.Commit();
}
catch (Exception)
{
// failed, Do something
} }

ASP.Net Web API Action Result

I'm building an ASP.Net Web API application and i have the following code...
public IHttpActionResult GetCustomers() {
var customers = context.Customers.ToList();
return Ok(customers);
}
I'm using the Ok() method to return customers because i'm using an IHttpActionResult return type.
Now if i have the following method
public void DeleteCustomer(int id) {
var customerInDb = context.Customers.SingleOrDefault(c => c.Id == id);
if (customerInDb == null) {
NotFound();
}
context.Customers.Remove(customerInDb);
context.SaveChanges();
}
Can I use NotFound() method here when the return type of my ActionMethod is void???
Void does not have a return type. So you can try to call NotFound(), but I'm not sure if this would even compile - Haven't tried. Why don't you just go with an out of the box IHttpActionResult?
public IHttpActionResult DeleteCustomer(int id)
{
var customerInDb = context.Customers.SingleOrDefault(c => c.Id == id);
if (customerInDb == null)
{
return NotFound();
}
context.Customers.Remove(customerInDb);
context.SaveChanges();
return Ok(customerInDb);
}
Using IHttpActionResult is the more elegant version. If the id is invalid, you can just safely exit your method and tell the calling client that something went wrong. If everything went well, you're just giving the client a thumbs-up. IF you return your deleted entity or just an empty Ok() should not matter at this point.
Using void may or may not delete the entity in your data storage. The client would never know, because the server would not return any response.

Unit Testing Web Api Controller that uses IHttpActionResult and ado.net

I'm trying to carry out some unit tests on my API Controller however I'm having issues since it is using IHttpActionResult and my actual data is called from a database (Azure).
Here's a sample of a simple Get by id method
// GET: api/RoundOnesApi/5
[ResponseType(typeof(RoundOne))]
public IHttpActionResult GetRoundOne(int id)
{
RoundOne roundOne = db.RoundOnes.Find(id);
if (roundOne == null)
{
return NotFound();
}
return Ok(roundOne);
}
I then tried to make a unit test to test this.
public void TestMethod1()
{
//ApplicationDbContext db = new ApplicationDbContext();
//arrange
var controller = new RoundOnesApiController();
//act
var actionResult = controller.GetRoundOne(1); //1
//assert
var response = actionResult as OkNegotiatedContentResult<RoundOne>; //RoundOne is my model class
Assert.IsNotNull(response);
Assert.AreEqual(1, response.Content.Id);
GetRoundOne(1) contains a database entry of a football teams information. Since this is not null I assumed it would pass.
By the way I'm just looking to do a general Unit Test to see if GetRoundOne(1) can be tested against actually existing. Once it passes that's all I need.
Exception:
Microsoft.VisualStudio.TestTools.UnitTesting.AssertFailedException was unhandled by user code
HResult=-2146233088
Message=Assert.IsNotNull failed.
Source=Microsoft.VisualStudio.QualityTools.UnitTestFramework
StackTrace:
at Microsoft.VisualStudio.TestTools.UnitTesting.Assert.HandleFailure(String assertionName, String message)
at Microsoft.VisualStudio.TestTools.UnitTesting.Assert.IsNotNull(Object value)
at MyUnitTestProject.UnitTest1.TestMethod1() in c:\Users\Daniel\Documents\Visual Studio 2013\Projects\UefaServiceV9\MyUnitTestProject\UnitTest1.cs:line 46
InnerException:

RedirectToAction usage in asp.net mvc

I want to post some questions about ASP.Net MVC. I am not familiar with web developing, But I was assigned to the web part of a project. We are doing the following: first, we create get & set properties for the person data:
public class Person
{
public int personID {get;set;}
public string personName {get;set;}
public string nric {get;set;}
}
and after login, we put the data in a class Person object and we use RedirectToAction like this:
return RedirectToAction("profile","person",new { personID = Person.personID});
It's working normally, but the parameter are shown in the URL. How can I hide them and also
can I hide the action name? Guide me the right way with some examples, please.
The parameter are shown in the URL because that is what the third parameter to RedirectToAction is - the route values.
The default route is {controller}/{action}/{id}
So this code:
return RedirectToAction("profile","person",new { personID = Person.personID});
Will produce the following URL/route:
/Person/Profile/123
If you want a cleaner route, like this (for example):
/people/123
Create a new route:
routes.MapRoute("PersonCleanRoute",
"people/{id}",
new {controller = "Person", action = "Profile"});
And your URL should be clean, like the above.
Alternatively, you may not like to use ID at all, you can use some other unique identifier - like a nickname.
So the URL could be like this:
people/rpm1984
To do that, just change your route:
routes.MapRoute("PersonCleanRoute",
"people/{nickname}",
new {controller = "Person", action = "Profile"});
And your action method:
public ActionResult Profile(string nickname)
{
}
And your RedirectToAction code:
return RedirectToAction("profile","person",new { nickname = Person.nickname});
Is that what your after?
If you don't want the parameter to be shown in the address bar you will need to persist it somewhere on the server between the redirects. A good place to achieve this is TempData. Here's an example:
public ActionResult Index()
{
TempData["nickname"] = Person.nickname;
return RedirectToAction("profile", "person");
}
And now on the Profile action you are redirecting to fetch it from TempData:
public ActionResult Profile()
{
var nickname = TempData["nickname"] as string;
if (nickname == null)
{
// nickname was not found in TempData.
// this usually means that the user directly
// navigated to /person/profile without passing
// through the other action which would store
// the nickname in TempData
throw new HttpException(404);
}
return View();
}
Under the covers TempData uses Session for storage but it will be automatically evicted after the redirect, so the value could be used only once which is what you need: store, redirect, fetch.
this may be solution of problem when TempData gone after refresh the page :-
when first time you get TempData in action method set it in a ViewData & write check as below:
public ActionResult Index()
{
TempData["nickname"] = Person.nickname;
return RedirectToAction("profile", "person");
}
now on the Profile action :
public ActionResult Profile()
{
var nickname = TempData["nickname"] as string;
if(nickname !=null)
ViewData["nickname"]=nickname;
if (nickname == null && ViewData["nickname"]==null)
{
throw new HttpException(404);
}
else
{
if(nickname == null)
nickname=ViewData["nickname"];
}
return View();
}
Temp data is capable of handling single subsequent request. Hence, value gone after refresh the page. To mitigate this issue, we can use Session variable also in this case. Try below:
public ActionResult Index(Person _person)
{
Session["personNickName"] = _person.nickName;
return RedirectToAction("profile", "person");
}
And in "profile" actionmethod:
public ActionResult profile()
{
Person nickName=(Person)Session["personNickName"];
if(nickName !=null)
{
//Do the logic with the nickName
}
}

Resources