My method which work
[HttpPost]
public async Task<ActionResult> DeleteTeam(int id)
{
Team team = await teamRepository.DeleteTeamAsync(id);
var fileToDeletePath = Path.Combine(Server.MapPath("~/Images/NBAlogoImg/"), team.Path);
if (System.IO.File.Exists(fileToDeletePath))
{
System.IO.File.Delete(fileToDeletePath);
}
if (team != null)
{
TempData["message"] = string.Format("{0} был удален", team.Name);
}
return RedirectToAction("Index", "Player");
}
It's my attempt to make a test, but unsuccessful
[TestMethod]
public async Task CanDeletePlayerAsync()
{
//Arrange
Mock<ITeamRepository> teamsMock = new Mock<ITeamRepository>();
Team team2 = new Team { Id = 2, Name = "Boston" , Path = "CHi.png" };
Team team3 = new Team { Id = 3, Name = "Lakers" };
string fullPath = ("~/Images/NBAlogoImg/");
var serverMock = new Mock<HttpServerUtilityBase>();
serverMock.Setup(x => x.MapPath(fullPath)).Returns(#"s:\work");
var httpContextMock = new Mock<HttpContextBase>();
httpContextMock.Setup(x => x.Server).Returns(serverMock.Object);
var mockFile = new Mock<IFileSystem>();
TeamController controller = new TeamController(teamsMock.Object);
controller.ControllerContext = new ControllerContext(httpContextMock.Object, new RouteData(), controller);
teamsMock.Setup(m => m.DeleteTeamAsync(team2.Id)).Returns(Task.FromResult(team2));
// Act
ActionResult result = await controller.DeleteTeam(team2.Id);
mockFile.Verify(x => x.File.Delete(#"s:\work\file.png"));
//Assert
Assert.IsInstanceOfType(result, typeof(RedirectToRouteResult));
}
I add the funcional to delete image from app if I delete the team. It works perfect, but how make a test by Moq I try some attempts by unsuccessfully.
I have the error message
Expected invocation on the mock at least once, but was never performed: x => x.File.Delete("s:\work\file.png")
No setups configured.
No invocations performed.
how it fix? I have downloaded IFileSystem and made a moq but verify have been not work.
One obvious solution would be to wrap your File.Delete Call in Custom Class, which implements a Custom interface, For example,
public interface IFileOperations
{
void Delete(string path);
}
For your System Operations, you can create a wrapper class.
public class SystemFileOperations:IFileOperations
{
public void Delete(string path)
{
File.Delete(path);
}
}
Now you can alter your original code to ensure SystemFileOperations is injected at all places where you would require IFileOperations.Delete.
private IFileOperations _fileOperations;
public ControllerName(IFileOperations operations)
{
_fileOperations = operations;
}
Following line would be then replaced
System.IO.File.Delete(fileToDeletePath);
with
_fileOperations.Delete(fileToDeletePath);
And for mocking , you could
var mock = new Mock<IFileOperations>();
mock.Verify(x=>x.Delete(path),Times.AtLeastOnce());
Please note that in your case, due to usage of File.Exists, you might have to mock that as well following the same pattern if you desire so
Related
In my test project i am using xUnit with Moq,
in my controller test class i also need an object of type Mock<UserManager<ApplicationUser>> to get the current user informations.
That's why i am using Moq to Setup the async method FindByIdAsync the setup is configured like this :
public async Task EntrsAdd_ShouldReturnSuccessJsonResult_WhenModelIdIsNull()
{
//Arrange
var id = _fixture.Create<string>();
ApplicationUser applicationUserMock = _fixture.Create<ApplicationUser>();
var viewModelMock = _fixture.Create<AddOrUpdateEntrViewModel>();
viewModelMock.Id = null;
_userManager.Setup(u => u.FindByIdAsync(id))
.Returns(Task.FromResult<ApplicationUser>(applicationUserMock));
//Act
var result = await _sutEntrsController.Add(viewModelMock).ConfigureAwait(false);
int test = 0;
//Assert
}
the method i am testing in the controller is:
public async Task<IActionResult> Add(AddOrUpdateEntrViewModel model)
{
var user = await _userManager.FindByIdAsync(model.UsrId);
var entr = _mapper.Map<Entr>(model);
entr.Usr = $"{user.FirstName} {user.LastName}";
if (model.Id is null)
{
await _entrService.AddAsync(entr);
}
else
{
await _entrService.UpdateAsync(entr);
}
return new JsonResult("success.");
}
The problem is that the setup uses the id variable and the corresponding property in the viewmodel is not set.
Either set the correct id for UsrId on the ViewModelMock or relax the setup by using It.IsAny<string>():
var viewModelMock = _fixture.Create<AddOrUpdateEntrViewModel>();
viewModelMock.Id = null;
viewModelMock.UsrId = id;
or
_userManager.Setup(u => u.FindByIdAsync(It.IsAny<string>()))
.Returns(Task.FromResult<ApplicationUser>(applicationUserMock));
My controller for the delete method :
[HttpDelete("{toDoListId}")]
public async Task<ActionResult> DeleteToDoList(int toDoListId)
{
var toDoListEntity = await _toDoListRepository.GetSpecificTodoAsync(toDoListId);
if (toDoListEntity == null)
{
return NotFound();
}
_toDoListRepository.DeleteToDoList(toDoListEntity);
await _toDoListRepository.SaveChangesAsync();
return NoContent();
}
My repository :
public async Task<ToDoList?> GetSpecificTodoAsync(int taskId)
{
return await _context.ToDoLists.Where(c => c.Id == taskId).FirstOrDefaultAsync();
}
public void DeleteToDoList(ToDoList toDoListDto)
{
_context.ToDoLists.Remove(toDoListDto);
}
My testcase for checking if the item got deleted and if it returns no content after being deleted. But both of my test cases are failing. Any help on how to write test cases for the delete part, I would be really grateful. I am also trying to test other methods but I am unfortunately stuck here. Please kindly help me
public class UnitTest1
{
private readonly Mock<IToDoListRepository> repositoryStub = new ();
private readonly Mock<IMapper> mapper = new Mock<IMapper>();
private readonly Random Rand = new();
private ToDoList GenerateRandomItem()
{
return new()
{
Id = Rand.Next(),
Description= Guid.NewGuid().ToString(),
Title = Guid.NewGuid().ToString(),
StartDate = DateTime.Now,
EndDate = DateTime.Now,
Done = false
};
}
[Fact]
public void Delete_removesEntry()
{
//arrange
var existingItem = GenerateRandomItem();
var controller = new ToDoController(repositoryStub.Object, mapper.Object);
var itemID = existingItem.Id;
//act
controller.DeleteToDoList(itemID);
//assert
Assert.Null(repositoryStub.Object.GetSpecificTodoAsync(itemID));
}
[Fact]
public async Task DeleteItemAsync_WithExistingItem_ReturnNoContent()
{
//Arrange
var existingItem = GenerateRandomItem();
repositoryStub.Setup(repo => repo.GetSpecificTodoAsync(existingItem.Id)).ReturnsAsync((existingItem));
var itemID = existingItem.Id;
var controller = new ToDoController(repositoryStub.Object, mapper.Object);
//Act
var result = await controller.DeleteToDoList(itemID);
//assert
result.Should().BeOfType<NoContentResult>();
}
Both test cases are failing because the mock has not been setup to behave as expected for each test case.
There is also a potential race condition in the shown tests since they are sharing the same mock instance. This will cause issues when setting up the mock as one test case could potentially override the setup of another case.
Update the tests so that they are isolated from each other.
In the first test, the expected behavior can be verified by checking the mock to see if the expected member was invoked.
[Fact]
public async Task DeleteToDoList_Should_RemoveEntry() {
//arrange
ToDoList existingItem = GenerateRandomItem();
var itemID = existingItem.Id;
Mock<IToDoListRepository> repositoryStub = new ();
//Setup expected behavior of mock
repositoryStub
.Setup(_ => _.GetSpecificTodoAsync(itemID))
.ReturnsAsync(existingItem);
var controller = new ToDoController(repositoryStub.Object, mapper.Object);
//act
await controller.DeleteToDoList(itemID);
//assert
repositoryStub.Verify(_ => _.DeleteToDoList(existingItem));
}
In the other test, the mock needs be setup to make sure the subject executes to completion.
[Fact]
public async Task DeleteToDoList_WithExistingItem_Should_ReturnNoContent() {
//Arrange
ToDoList existingItem = GenerateRandomItem();
var itemID = existingItem.Id;
Mock<IToDoListRepository> repositoryStub = new ();
//Setup expected behavior of mock
repositoryStub
.Setup(_ => _.GetSpecificTodoAsync(itemID))
.ReturnsAsync(existingItem);
repositoryStub.Setup(_ => _.SaveChangesAsync()).ReturnsAsynt(true);
var controller = new ToDoController(repositoryStub.Object, mapper.Object);
//Act
ActionResult result = await controller.DeleteToDoList(itemID);
//assert
result.Should().BeOfType<NoContentResult>();
}
I'm trying to write a Unit test case for the delete method of the below controller
public class AssetProxyController : Controller
{
private IRiskAssetProxyService _assetProxyService;
public AssetProxyController( IRiskAssetProxyService assetProxyService)
{
_assetProxyService = assetProxyService;
}
[HttpDelete("{assetId}")]
public ActionResult Delete(string assetId)
{
if (_assetProxyService.DeleteAssetProxyMapping(assetId))
{
return Ok("AssetProxy Deleted");
}
else
{
return BadRequest("Unable to delete AssetProxy");
}
}
}
Test
[TestMethod]
public void Delete_ShouldReturnDeleteAssetProxy()
{
//Mock
var mockContext = new Mock<MainDBContext>();
var faker = AutoFaker.Create();
var assetProxyMappings = faker.Generate<List<AssetProxyMapping>>();
var mockAssetProxyDbSet = GetQueryableMockDbSet<AssetProxyMapping>(assetProxyMappings);
mockContext.Setup(c => c.AssetProxyMapping).Returns(mockAssetProxyDbSet);
//Test
var mocklogger = new Mock<ILogger<RiskDataControllerDbAccess>>();
var positiondbaccess = new Mock<RiskAssetProxyDbAccess>(mockContext,mocklogger);
var mockserviceLogger = new Mock<ILogger<RiskAssetProxyService>>();
var positionService = new Mock<RiskAssetProxyService>(positiondbaccess, mockserviceLogger);
var positionController = new AssetProxyController(positionService.Object);
Assert.AreEqual(true, true);
}
But i keep getting the exception
Test method Risk.DataService.UnitTests.API.AssetProxyControllerTests.Delete_ShouldReturnDeleteAssetProxy threw exception:
Castle.DynamicProxy.InvalidProxyConstructorArgumentsException: Can not instantiate proxy of class: Risk.DataServices.RiskAssetProxyService.
Could not find a constructor that would match given arguments:
Moq.Mock`1[Risk.DataServices.RiskAssetProxyDbAccess]
Moq.Mock`1[Microsoft.Extensions.Logging.ILogger`1[Risk.DataServices.RiskAssetProxyService]]
The currently shown test is completely over-engineered based on the shown subject under test.
Given the shown Delete controller action, the test case should be simplified
[TestMethod]
public void AssetProxy_Delete_Should_Return_Ok() {
//Arrange
string assetId = "assetId";
var serviceMock = new Mock<IRiskAssetProxyService>();
serviceMock.Setup(_ => _.DeleteAssetProxyMapping(assetId))
.Returns(true);
AssetProxyController controller = new AssetProxyController(serviceMock.Object);
//Act
ActionResult result = controller.Delete(assetId);
//Assert - using fluent assertions
result.Should().NotBeNull();
string expected = "AssetProxy Deleted";
OkObjectResult okResult = result as OkObjectResult;
okResult.Should().NotBeNull();
string actual = okResult.Value as string;
actual.Should().Be(expected); //same as Assert.AreEqual(expected, actual);
}
Another test case can be done to assert bad requests and would be similar to the case shown above except that the mocked service will be arranged to return false in order to cause the expected behavior and the assertion updated accordingly
I started using xUnit and NSubstitute for my unit tests. I want to mock the following method.
public async Task<DecorationModel> GetDecorationWithId(string userId, string decorationId)
{
var decoration = await _db.Decorations
.Include(d => d.BgImage)
.FirstOrDefaultAsync(d => d.Id == decorationId);
if (decoration == null || decoration.OwnerId != userId)
return null;
return new DecorationModel
{
Id = decoration.Id,
Name = decoration.Name,
// Other stuff
};
}
I attempted it but couldn't get it to work. My current test class is as follows;
public class DecorationServiceTests
{
private readonly DecorationService _subject;
private readonly IAppDbContext _db = Substitute.For<IAppDbContext>();
private readonly DbSet<Decoration> _decorationDbSet = Substitute.For<DbSet<Decoration>, IQueryable<Decoration>>();
public DecorationServiceTests()
{
_subject = new DecorationService(_db);
}
[Fact]
public async Task GetDecorationWithId_ShouldReturnDecoration_WhenExists()
{
// Arrange
var userId = new Guid().ToString();
var decorationId = new Guid().ToString();
var decorations = new List<Decoration>()
{
new Decoration()
{
Id = decorationId,
Name = "",
OwnerId = userId,
}
};
_db.Decorations.Returns(_decorationDbSet);
_decorationDbSet.FirstOrDefaultAsync(t => t.Id == decorationId).Returns(decorations.FirstOrDefault());
// Act
var result = await _subject.GetDecorationWithId(userId, decorationId);
// Assert
Assert.Equal(result.Id, decorations[0].Id);
}
}
However, I get the following error:
"The provider for the source 'IQueryable' doesn't implement 'IAsyncQueryProvider'. Only providers that implement 'IAsyncQueryProvider' can be used for Entity Framework asynchronous operations."
I searched on the web but couldn't find a good reference. How can I solve this?
I think you'll be going through a whole lot of pain and suffering if you are trying to mock DbSet. That comes straight from the docs of EFCore: https://learn.microsoft.com/en-us/ef/core/testing/#unit-testing
Instead, you should be trying to use a real db or in-memory one.
See the testing sample here: https://learn.microsoft.com/en-us/ef/core/testing/testing-sample
In our MVC4 application with Entity Framework 4.0 based on the Music Store Tutorial we are using Moq to mock the DbContext and unit test are logic. One of our methods proves difficult to test though since it makes use of HttpContext or HttpContextBase. One example method looks like this:
public static ShoppingCart GetCart(HttpContextBase context)
{
var cart = new ShoppingCart();
cart.ShoppingCartId = cart.GetCartId(context);
return cart;
}
The only property collected from HttpContextBase is the [CartSessionKey] as can be seen here:
public string GetCartId(HttpContextBase context)
{
if (context.Session[CartSessionKey] == null)
{
if (!string.IsNullOrWhiteSpace(context.User.Identity.Name))
{
context.Session[CartSessionKey] =
context.User.Identity.Name;
}
else
{
// Generate a new random GUID using System.Guid class
Guid tempCartId = Guid.NewGuid();
// Send tempCartId back to client as a cookie
context.Session[CartSessionKey] = tempCartId.ToString();
}
}
return context.Session[CartSessionKey].ToString();
}
We have heard horror stories that HttpContext is a very complex class and that if you print it you have enough paper to circle the earth eight times.
Nevertheless we want to mock it. The question is how. The properties that we want to mock are the [CartSessionKey], and the property that come from the context as contest.User.Identity.Name.
We suspect we need to use something like this:
var mockData = new Mock<FakeContext>();
mockData.Setup(m => m.Orders).Returns(memoryOrderItems);
mockData.Setup(m => m.Carts).Returns(memoryCartItems);
Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
Mock<HttpRequestBase> mockHttpRequest = new Mock<HttpRequestBase>();
mockHttpRequest.Setup(x => x.CartSessionKey).Returns(1);
mockHttpContext.Setup(x => x.Request).Returns(mockHttpRequest.Object);
but we cannot find how to specifically implement this so we do not get any errors on methods that use context.Session[CartSessionKey] or context.User.Identity.Name.
We hope someone can help us out.
/edit
When we do this:
var memoryUserItems = new FakeDbSet<User>()
{
new User { Email = "test#test.de",
FullName = "Test Person",
isAvailable = true,
Name = "WHat"
},
new User { Email = "test2#test.de",
FullName = "Test Person 2",
isAvailable = true,
Name = "WHat 2"
}
};
(...) Other memory...Items
And then this:
// Create mock units of work
var mockData = new Mock<FakeContext>();
mockData.Setup(m => m.Orders).Returns(memoryOrderItems);
mockData.Setup(m => m.Carts).Returns(memoryCartItems);
mockData.Setup(m => m.Users).Returns(memoryUserItems);
var principalMock = new Mock<IPrincipal>();
var identityMock = new Mock<IIdentity>();
var userMock =
identityMock.Setup(x => x.Name).Returns("Test!");
identityMock.Setup(x => x.IsAuthenticated).Returns(true); // optional ;)
mockData.Setup(x => x.Identity).Returns(identityMock.Object);
var httpReqBase = new Mock<HttpRequestBase>(); // this is useful if you want to test Ajax request checks or cookies in the controller.
var httpContextBase = new Mock<HttpContextBase>();
httpContextBase.Setup(x => x.User).Returns(principalMock.Object);
httpContextBase.Setup(x => x.Session[It.IsAny<string>()]).Returns(1); //Here is the session indexer. You can swap 'any' string for specific string.
httpContextBase.Setup(x => x.Request).Returns(httpReqBase.Object);
We get the error that:
Error 3 'project.Models.FakeContext' does
not contain a definition for 'Identity' and no extension method
'Identity' accepting a first argument of type
'project.Models.FakeContext' could be found
(are you missing a using directive or an assembly
reference?)
/ edit2
To make it more clear. The actual method I am testing is the following:
public ActionResult Complete(int id)
{
// Make sure that user is currentuser and otherwise bring user to our Thief page
if (id != db.GetCurrentUserId())
{
return View("Thief");
}
var cart = ShoppingCart.GetCart(this.HttpContext);
var currentDate = DateTime.Today;
var viewModel = new ShoppingCartViewModel
{
CartItems = cart.GetCartItems(),
CartTotal = cart.GetTotal(),
ProductItems = db.Products.ToList()
};
if (viewModel.CartItems.Count() == 0)
{
return View("Empty");
}
// Try to write cart to order table
try
{
foreach (var item in viewModel.CartItems)
{
ProcessOrder(item, id, currentDate);
}
// after this we empty the shopping cart
cart.EmptyCart();
return View();
}
catch
{
// Invalid - display error page
return View("Error");
}
}
As can be seen the var cart = ShoppingCart.GetCart(this.HttpContext); uses this.HttpContext. In the test I just do controller.Complete(1). I cannot pass a new HttpContext to the controller I guess?
/ edit 3
While using the code below with the mocks I get the following message:
Test Name: TestCheckoutCompleteShouldWithEmptyCart
Test FullName: Controllers.CheckoutControllerTest.TestCheckoutCompleteShouldWithEmptyCart
Test Source: Controllers\CheckoutControllerTest.cs : line 141
Test Outcome: Failed
Test Duration: 0:00:00.0158591
Result Message:
Test method Controllers.CheckoutControllerTest.TestCheckoutCompleteShouldWithEmptyCart threw exception:
System.NullReferenceException: Object reference not set to an instance of an object.
Result StackTrace:
at Models\ShoppingCart.cs:line 170
at \Models\ShoppingCart.cs:line 20
at \Controllers\CheckoutController.cs:line 48
at Controllers\CheckoutControllerTest.cs:line 143
OK, here it goes. The following works in MVC5 with AD, I'm not sure if it's fully backwards compatible, you'll have to check.
var principalMock = new Mock<IPrincipal>();
var identityMock = new Mock<IIdentity>();
identityMock.Setup(x => x.Name).Returns("Test!");
identityMock.Setup(x => x.IsAuthenticated).Returns(true); // optional ;)
userMock.Setup(x => x.Identity).Returns(identityMock.Object);
var httpReqBase = new Mock<HttpRequestBase>(); // this is useful if you want to test Ajax request checks or cookies in the controller.
var httpContextBase = new Mock<HttpContextBase>();
httpContextBase.Setup(x => x.User).Returns(principalMock.Object);
httpContextBase.Setup(x => x.Session[It.IsAny<string>()]).Returns(1); //Here is the session indexer. You can swap 'any' string for specific string.
httpContextBase.Setup(x => x.Request).Returns(httpReqBase.Object);
This would help you to write a proper Unit Test using Moq.
[TestClass]
public class SutTest
{
[TestMethod]
public void GetCartId_WhenUserNameIsNotNull_SessionContainsUserName()
{
var httpContextStub = new Mock<HttpContextBase>();
var httpSessionStub = new Mock<ISessionSettings>();
httpSessionStub.Setup(x => x.Get<string>(It.IsAny<string>())).Returns(() => null);
httpSessionStub.SetupSequence(x => x.Get<string>(It.IsAny<string>()))
.Returns(null)
.Returns("FakeName");
var httpUserStub = new Mock<IPrincipal>();
var httpIdenttyStub = new Mock<IIdentity>();
httpUserStub.SetupGet(x => x.Identity).Returns(httpIdenttyStub.Object);
httpIdenttyStub.SetupGet(x => x.Name).Returns("FakeName");
httpContextStub.Setup(x => x.User).Returns(httpUserStub.Object);
var sut = new Sut(httpSessionStub.Object);
var result = sut.GetCartId(httpContextStub.Object);
Assert.AreEqual("FakeName",result );
}
}
Check the SetupSequence method which gives you find Control over different values being return on he same stubbed call.
Also important to decouple your session from HttpContext as you can always run into issues.
public class SessionSettings : ISessionSettings
{
private readonly HttpSessionStateBase _session;
public SessionSettings(HttpSessionStateBase session)
{
_session = session;
}
public T Get<T>(string key)
{
return (T)_session[key];
}
public void Set<T>(string key, T value)
{
_session[key] = value;
}
}
public interface ISessionSettings
{
T Get<T>(string key);
void Set<T>(string key, T value);
}
public class Sut
{
private ISessionSettings _sessionSettings;
public Sut(ISessionSettings sessionSettings)
{
_sessionSettings = sessionSettings;
}
public string GetCartId(HttpContextBase context)
{
if (_sessionSettings.Get<string>(CartSessionKey) == null)
{
if (!string.IsNullOrWhiteSpace(context.User.Identity.Name))
{
_sessionSettings.Set<string>(CartSessionKey, context.User.Identity.Name);
}
else
{
// Generate a new random GUID using System.Guid class
Guid tempCartId = Guid.NewGuid();
// Send tempCartId back to client as a cookie
_sessionSettings.Set<string>(CartSessionKey, tempCartId.ToString());
}
}
return _sessionSettings.Get<string>(CartSessionKey);
}
private string CartSessionKey = "key";
}
This way the code is more readable and easier to understand.