I am trying to write a unit test for a method that relies on a dependency which offers a method that accepts an object and modifies it, but does not return it on a "new path", e.g. as a return value or on a by reference parameter.
public class Product
{
public string Name { get; set; }
}
public interface IFixer
{
void Modify(Product product);
}
public class Fixer: IFixer
{
public void Modify(Product product)
{
if (string.IsNullOrEmpty(product.Name))
{
product.Name = "Default";
}
}
}
public class Manager()
{
private readonly IFixer _fixer;
public Manager(IFixer fixer)
{
_fixer = fixer;
}
public bool IsProductNew(int id)
{
var product = GetProduct(id); // Gets an object instance from a repository, e.g. a file or a database, so we can have something to operate on.
_fixer.Modify(product);
return product.Name != "Default";
}
}
So I want to be able to test my Manager class' IsProductNew() method:
var fakeFixer = A.Fake<IFixer>();
var manager = new Manager(fakeFixer);
var isNew = manager.IsProductNew(A<int>._);
Assert.True(isNew);
What I am missing here is: How do I mock the behaviour of IFixer.Modify(), i.e. have it modify a Product object instance?
Answering this effectively is dependent on the definition of GetProduct(id);
If however the IFixer implementation has no knock on effects or undesirable behavior then there really is no need to mock it.
//Arrange
var fixer = new Fixer();
var manager = new Manager(fixer);
//Act
var isNew = manager.IsProductNew(1);
//Assert
Assert.True(isNew);
But to answer
How do I mock the behaviour of IFixer.Modify(), i.e. have it modify a Product object instance?
you would need a callback that captures the matched parameter
//Arrange
var fakeFixer = A.Fake<IFixer>();
A.CallTo(() => fakeFixer.Modify(A<Product>._))
.Invokes((Product arg) => arg.Name = "Not Default Name");
var manager = new Manager(fakeFixer);
//Act
var isNew = manager.IsProductNew(1);
//Assert
Assert.True(isNew);
Reference Invoking Custom Code
So I want to be able to test my Manager class' IsProductNew() method
You cannot test it effectively in its current form. You will not get 100% coverage. You should either:
pass the product directly:
public bool IsProductNew(Product product)
{
_fixer.Modify(product);
return product.Name != "Default";
}
which you can test by:
var fakeFixer = A.Fake<IFixer>();
var manager = new Manager(fakeFixer);
var product = new Product { Id = 100, Name = "Default" }; // create another test for where Name != "Default"
var isNew = manager.IsProductNew(product);
// Assert that fixer.modify is called with the product
A.CallTo(() => fakeFixer.Modify(A<product>.That.Matches(p => p.Id == 100))).ShouldHaveBeenCalledOnceExactly();
// Should return false because of product name = "Default"
Assert.IsFalse(isNew);
or, pass a "product getter" into the Manager() constructor:
public Manager(IFixer fixer, IProductGetter productGetter)
{
_fixer = fixer;
_productGetter = productGetter;
}
...
public bool IsProductNew(int id)
{
var product = _productGetter.GetProduct(id);
_fixer.Modify(product);
return product.Name != "Default";
}
which you can test by:
var fakeProductGetter = A.Fake<IProductGetter>();
// Prime the product getter to return a product
A.CallTo(() => fakeProductGetter.Get(A<int>.Ignored))
.Returns(new Product{
Id = 100,
Name = "Default" // create another test for where Name != "Default"
});
var fakeFixer = A.Fake<IFixer>();
var manager = new Manager(fakeFixer, fakeproductGetter);
var isNew = manager.IsProductNew(100);
// Assert that a call is made to productGetter.Get with the ID
A.CallTo(() => productGetter.Get(100)).MustHaveHapennedOnceExactly();
// Assert that fixer.modify is called with the product
A.CallTo(() => fakeFixer.Modify(A<product>.That.Matches(p => p.Id == 100))).ShouldHaveBeenCalledOnceExactly();
// Should return false because of product name = "Default"
Assert.IsFalse(isNew);
The choice you make depends on whether you want the IsProductNew() method to be responsible for getting the product by the ID, or if you want to just pass the product directly.
Related
Here is my code:
public IActionResult Post([FromBody] ApiPermission apiClient)
{
return Ok(_apiPermissionService.Add(apiClient, GetCurrentUserFullName(_httpContextAccessor.HttpContext.User)));
}
I need the result of GetCurrentUserFullName which has the definition of
internal string GetCurrentUserFullName(ClaimsPrincipal principal)
{
if (principal == null)
throw new ArgumentNullException(nameof(principal));
return principal.FindFirstValue("UserFullName");
}
How we can pass GetCurrentUserFullName to pass the test.
Below is my testing code:
public void Post()
{
Mock<ApiPermissionGroup> mockApiPermissionGroup = new Mock<ApiPermissionGroup>();
var apiPermission = new ApiPermission
{
ApiPermissionId = 1,
Name = "Name",
Description = "Description",
AddedBy = "AddedBy",
AddedDate = DateTime.UtcNow,
ModifiedBy = "ModifiedBy",
ModifiedDate = DateTime.UtcNow,
ApiPermissionGroupId = 2,
ApiPermissionGroup = mockApiPermissionGroup.Object
};
List<ApiPermission> lstApiPermission = new List<ApiPermission>();
lstApiPermission.Add(apiPermission);
ApiPermissionController ApiPermissionController = new ApiPermissionController(_mockApiPermissionService.Object, _mockHttpContextAccessor.Object);
var result = ApiPermissionController.Post(apiPermission);
Assert.IsNotNull(result);
}
When running the above code I am getting the error " System.NullReferenceException : Object reference not set to an instance of an object." at
Microsoft.AspNetCore.Http.IHttpContextAccessor.HttpContext.get returned null.
In this specific case, I'd not mock the internal method, but mock the context accessor, e.g.:
// Set up mock principal
var principal = new Mock<ClaimsPrincipal>();
principal
.Setup(r => r.FindFirst("UserFullName"))
.Returns(new Claim("UserFullName", "TEST_USER_NAME"));
// Create dummy context
var context = new DefaultHttpContext { User = principal.Object };
// Set up mock context accessor
var mockCtxAcc = new Mock<IHttpContextAccessor>();
mockCtxAcc.SetupGet(x => x.HttpContext).Returns(context);
// Create controller under test
var ctrl = new MyController(mockCtxAcc.Object, ...);
The unit test should test the behavior of the class as it is, but without the dependencies of the class. This way, you test the code that the controller runs in real life scenario - also the internal method as it is.
I am trying to test a method that is using two different interfaces.
Using Moq I configure the interfaces methods and set a return object, but just the first method executed returns value, the second returns null, no matter what I set as Returns.
This is an example :
Interface 1
public interface IUserRepository
{
User GetUserById(int id);
}
Interface 2
public interface ICallApiService
{
ApiResponseDto ValidateUser();
}
Class I want to test
public class UserServices : IUserServices
{
private IUserRepository _userRepository;
private ICallApiService _callApiService;
public UserServices(IUserRepository userRepository, ICallApiService callApiService)
{
_userRepository = userRepository;
_callApiService = callApiService;
}
public User GetUserById(int id)
{
//result always have a value set to result
var result = _callApiService.ValidateUser();
//result2 is always null
var result2 = _userRepository.GetUserById(result.UserId);
return result2;
}
}
The Test Method
[TestMethod]
public void TestMethod1()
{
moqUserRepository = new Moq.Mock<IUserRepository>();
moqUserRepository.Setup(s => s.GetUserById(1)).Returns(new User() { Id = 100, Birth = DateTime.Now, Email = "g#test.com", Name="g" });
moqCallApiService = new Moq.Mock<ICallApiService>();
moqCallApiService.Setup(s => s.ValidateUser()).Returns(new ApiResponseDto() { Active = true, Messages = "None", UserId = 100 });
var userService = new UserServices(moqUserRepository.Object, moqCallApiService.Object);
var resultInstance = userService.GetUserById(1);
var moqUserService = new Moq.Mock<UserServices>(moqUserRepository.Object, moqCallApiService.Object).Object;
var resultMock = moqUserService.GetUserById(1);
}
In both cases using instance and mock i get the same error (return null).
Am I missing something to Moq ?
You're instructing the repository mock to return an object when the input parameter id has the value 1. But the service code calls the repository with the value of result.UserId which is set to be 100 by the other mock setup call.
Change the first setup call to
moqUserRepository.Setup(s => s.GetUserById(100)).Returns(new User() { Id = 100, Birth = DateTime.Now, Email = "g#test.com", Name="g" });
Or use It.IsAny, since you don't really care about the value when you only have a single mocked call to the method:
moqUserRepository.Setup(s => s.GetUserById(It.IsAny<int>())).Returns(new User() { Id = 100, Birth = DateTime.Now, Email = "g#test.com", Name="g" });
I've got a class that has the following implementation:
public sealed class HotelRepository : IHotelRepository
{
private readonly string _dataSource;
public HotelRepository(string dataSource) => _dataSource = dataSource;
/// <inheritdoc />
public async Task<IEnumerable<Hotel>> GetAllAsync() =>
await Task.Run(() => JObject.Parse(File.ReadAllText(_dataSource))["hotels"].ToList().Select(x => x.ToObject<Hotel>()));
/// <inheritdoc />
public async Task<IEnumerable<Hotel>> GetListByMatchAsync(string name) =>
await GetAllAsync().ContinueWith(x => x.Result.Where(y => y.Name.Contains(name, StringComparison.CurrentCultureIgnoreCase)));
}
As you can see, the GetListByMatchAsync method calls GetAllAsync, then does some logic before returning the result.
When I tried to mock this repository for unit testing, I'm struggling to get a result out of GetListByMatchAsync as it always fails as a null reference exception.
Here's the unit test:
[TestCase("Test", "X")]
[TestCase("Hotel", "X")]
[TestCase("Name", "X")]
public async Task GetListByMatchAsync_GetHotelListByMatchingNameAsync_ReturnsFiveMatchingHotels(string name, string nonMatch)
{
_hotelRepositoryMock = new Mock<IHotelRepository>();
_hotelRepository = _hotelRepositoryMock.Object;
// Set up sample data.
var data = new List<Hotel>
{
new Hotel{Id = 1, Name = $"{name}", Description = "Description2", Location = "Location2", Rating = Rating.Two},
new Hotel{Id = 2, Name = $"{name.ToUpper()}", Description = "Description1", Location = "Location1", Rating = Rating.Five},
new Hotel{Id = 3, Name = $"{name.ToLower()}", Description = "Description2", Location = "Location2", Rating = Rating.Three},
new Hotel{Id = 4, Name = $"{name} {nonMatch}", Description = "Description2", Location = "Location2", Rating = Rating.One},
new Hotel{Id = 5, Name = nonMatch, Description = "Description2", Location = "Location2", Rating = Rating.One},
};
// Set up mock methods and ensure these method returns any sample data.
_hotelRepositoryMock.Setup(x => x.GetListByMatchAsync(It.IsAny<string>()));
_hotelRepositoryMock.Setup(x => x.GetAllAsync()).ReturnsAsync(data);
var result = await _hotelRepository.GetListByMatchAsync(name);
// Cast to list to make assertions.
var hotels = result.ToList();
Assert.That(hotels, Is.TypeOf<List<Hotel>>());
Assert.That(hotels.Count, Is.EqualTo(4));
}
How can I make this test work such that the GetListByMatchAsync mock method does some logic after calling the mocked GetAllAsync method?
First, the code that you have shown cannot possibly work because the methods that you are trying to set up are not virtual. Declare the methods you want to Setup as virtual.
Second, this is wrong:
_hotelRepositoryMock.Setup(x => x.GetListByMatchAsync(It.IsAny<string>()));
With this call, you're effectively setting up GetListByMatchAsync to return default(Task<IEnumerable<Hotel>>), i.e. null. That's obviously not what you want. Either:
use .Returns(...) to specify what the method should return; or,
use .CallBase() if the method should simply return whatever the implementation in the base class would return. (This is likely what you need.)
I have the following three methods in the CompanyApplication class (along with the supporting factories and services listed):
public ResultSet<CompanyDto> AddCompany(CompanyDto companyDto)
{
var result = new CompanyDto();
var company = new Company();
Mapper.Map(companyDto, company);
using (ITransaction t = _transactionFactory.Create())
{
company = _companyService.Add(company);
t.Commit();
}
Mapper.Map(company, result);
return new ResultSet<CompanyDto>(1, new[] { result });
}
public ResultSet<CompanyContactDto> AddCompanyContact(CompanyContactDto companyContactDto)
{
var result = new CompanyContactDto();
var company = new Company();
var contact = new CompanyContact();
Mapper.Map(companyContactDto, contact);
using (ITransaction t = _transactionFactory.Create())
{
var contactCompanies = FindByIdJoin<Company, CompanyDto>(companyContactDto.CompanySK);
Mapper.Map(contactCompanies.Data.First(), company);
company.CompanyContacts.Add(contact);
company = _companyService.Update(company);
t.Commit();
}
Mapper.Map(contact, result);
return new ResultSet<CompanyContactDto>(1, new[] { result });
}
public ResultSet<T_DtoType> FindByIdJoin<T_DbType, T_DtoType>(long id)
{
IAbstractRepository<T_DbType> repository = EnsureRepository<T_DbType>();
T_DbType entity = repository.FindByIdJoin(id);
return (entity == null ? null : MapResultSetToDto<T_DbType, T_DtoType>(entity));
}
There are other objects in play here, which is why the FindByIdJoin has been made a separate method in the CompanyApplication class.
I have set up the testing class with some mocks and an instance of the CompanyApplication class:
private Mock<ICompanyRepository> _mockCompanyRepository;
private Mock<ICompanyDomain> _mockCompanyService;
private Mock<ITransactionFactory> _mockTransactionFactory;
private Mock<ITransaction> _mockTransaction;
private CompanyApplication _companyApplication;
[Setup]
public void SetUp()
{
_mockCompanyRepository = new Mock<ICompanyRepository>(MockBehavior.Strict);
_mockCompanyService = new Mock<ICompanyDomain>(MockBehavior.Strict);
_mockTransactionFactory = new Mock<ITransactionFactory>(MockBehavior.Strict);
_mockTransaction = new Mock<ITransaction>(MockBehavior.Strict);
_companyApplication = new CompanyApplication(
_mockCompanyRepository.Object,
_mockCompanyService.Object,
_mockTransactionFactory.Object);
}
I am successfully able to test the FindByIdJoin and AddCompany methods directly in Moq like this:
[Test]
public void CanFindCompanyByIdJoin()
{
var data = new Company {ObjectId = 1, Name = "Company1"};
_mockCompanyRepository.Setup(x => x.FindByIdJoin(It.Is<long>(arg => arg == data.ObjectId)))
.Returns(data);
var result = _companyApplication.FindByIdJoin<Company, CompanyDto>(data.ObjectId);
Assert.AreEqual(data.ObjectId, result.Data.First().ObjectId);
}
[Test]
public void CanAddCompany()
{
var data = new Company {ObjectId = 1, Name = "Company1"};
_mockCompanyService.Setup(x => x.Add(It.Is<Company>(arg => arg.ObjectId == data.ObjectId)))
.Returns(data);
_mockTransactionFactory.Setup(x => x.Create()).Returns(_mockTransaction.Object);
_mockTransaction.Setup(x => x.Commit());
_mockTransaction.Setup(x => x.Dispose());
var dto = new CompanyDto {ObjectId = 1, Name = "Company1"};
var result = _companyApplication.AddCompany(dto);
_mockCompanyService.Verify(t => t.Add(It.IsAny<Company>()));
}
Those two tests pass just fine. However, I'm having trouble coming up with a test for AddCompanyContact, because it calls FindByIdJoin as part of its flow, and that seems to be getting in the way.
Specifically, is there a way to mock var contactCompanies = FindByIdJoin<Company, CompanyDto>(companyContactDto.CompanySK) in a test for the AddCompanyContact method?
Thanks!
There is two alternatives that i see depending on the amount of work that you want to do.
Wrap that call into a object and instantiate it using a IOC container. This is the one that i feel would take the most effort if you are not using one already.
Turn that call into a Func and make a method without that parameter that does the call. This approach has the disadvantage that the top call will be untestable but will allow access to the rest of the method.
Example Below:
public ResultSet<CompanyContactDto> AddCompanyContact(CompanyContactDto companyContactDto)
{
AddCompanyContact(CompanyContactDto, ()=>
{
return FindByIdJoin<Company, CompanyDto> companyContactDto.CompanySK);
}
}
public ResultSet<CompanyContactDto> AddCompanyContact(CompanyContactDto companyContactDto, Func<WhateverTheMethodReturns> findIdReplacement)
{
var result = new CompanyContactDto();
var company = new Company();
var contact = new CompanyContact();
Mapper.Map(companyContactDto, contact);
using (ITransaction t = _transactionFactory.Create())
{
var contactCompanies = findIdReplacement();
Mapper.Map(contactCompanies.Data.First(), company);
company.CompanyContacts.Add(contact);
company = _companyService.Update(company);
t.Commit();
}
Mapper.Map(contact, result);
return new ResultSet<CompanyContactDto>(1, new[] { result });
}
I was over complicating the problem... since AddCompanyContact calls FindByIdJoin, all I needed to do was mock the same interface that is used in FindByIdJoin.
Lesson learned: mock interfaces, not classes.
I want to unit test a controller that takes a FormCollection and has the Find method. I have successfully implemented a fake DbContext and a FakeDbSet (from here) so that I can use the fake objects in my tests.
The specific method I want to test looks like this:
//
// POST: /Order/SettleOrders
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult SettleOrders(FormCollection c)
{
int i = 0;
if (ModelState.IsValid)
{
var ordersCollection = new List<Order>();
// Get values of form
var OrderIdArray = c.GetValues("item.OrderId");
var UnitPriceArray = c.GetValues("item.UnitPrice");
for (i = 0; i < OrderIdArray.Count(); i++)
{
// Find order in database and update the unitprice and isconfirmed status
Order order = db.Orders.Find(Convert.ToInt32(OrderIdArray[i]));
order.UnitPrice = Convert.ToDecimal(UnitPriceArray[i]);
order.IsConfirmed = true;
db.SetModified(order);
ordersCollection.Add(order);
}
db.SaveChanges();
}
// Return orders of this date to view
var currentDate = DateTime.Today;
var orders = db.Orders.Include(o => o.Product)
.Include(o => o.User)
.Where(o => o.Date == currentDate);
return View("Confirmation", orders.ToList().OrderBy(o => o.User.Name));
}
This is how I set up the test of my OrderController using Moq:
[TestInitialize]
public void OrderControllerTestInitialize()
{
// Arrange
var unconfirmedMemoryItems = new FakeOrderSet
{
// #TODO Tests/methods should ideally not be dependent on DateTime.Today...
new Order { OrderId = 1, UnitPrice = 1.00M, Quantity = 2, Date = DateTime.Today, IsConfirmed = false },
new Order { OrderId = 2, UnitPrice = 2.00M, Quantity = 1, Date = DateTime.Today, IsConfirmed = false }
};
// Create mock unit of work
var unconfirmedMockData = new Mock<ISeashellBrawlContext>();
unconfirmedMockData.Setup(m => m.Orders).Returns(confirmedMemoryItems);
// Setup controller
unconfirmedOrderController = new OrderController(confirmedMockData.Object);
}
Then the test goes likes this to confirm the unconfirmed orders are becoming confirmed.
[TestMethod]
public void TestSettleOrdersPost()
{
// Invoke
FormCollection form = CreatesettleOrdersPostFormCollection();
var viewResult = unconfirmedOrderController.SettleOrders(form) as ViewResult;
var ordersFromView = (IEnumerable<Order>)viewResult.Model;
// Assert
Assert.AreEqual(3, ordersFromView.ElementAt(0).Quantity,
"New item should be added to older one since it has same index and is of same date");
Assert.AreEqual(true, ordersFromView.ElementAt(0).IsConfirmed,
"The item should also be set to confirmed");
}
// Helper methods
private static FormCollection CreatesettleOrdersPostFormCollection()
{
FormCollection form = new FormCollection();
form.Add("item.OrderId", "1");
form.Add("item.UnitPrice", "2.00");
form.Add("item.OrderId", "2");
form.Add("item.UnitPrice", "3.00");
return form;
}
Unfortunately I get the following error message:
Test Name: TestSettleOrdersPost
Result Message:
System.NullReferenceException: Object reference not set to an instance
of an object. Result StackTrace: at
Controllers.OrderController.SettleOrders(FormCollection c) in
Controllers\OrderController.cs:line 121
This probably has to do with the fake Find method not doing what it is supposed to do. The find method I use looks like this:
class FakeOrderSet : FakeDbSet<Order>
{
public override Order Find(params object[] keyValues)
{
var id = Convert.ToInt32(keyValues[0]);
Order o = null;
IQueryable<Order> keyQuery = this.AsQueryable<Order>();
foreach (var order in keyQuery)
{
if (order.OrderId == id)
{
o = order;
}
}
return o;
}
}
I have no idea how to improve this code. Looking at it I am convinced it should work. Since I am a 7-day noob to unit testing this conviction is of not much worth though. I hope someone can help me out.
I think you can include Find method implementation in your generic FakeDbSet<T> class.
For example if all your entites have property Id that is mapped to primary key in database you can do following:
public class FakeDbSet<T>
{
....
public T Find(params object[] keyvalues)
{
var keyProperty = typeof(T).GetProperty(
"Id",
BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase);
var result = this.SingleOrDefault(obj =>
keyProperty.GetValue(obj).ToString() == keyValues.First().ToString());
return result;
}
}
If your key property is not Id you can try to determine some algorith how to recognize key property for the entity via reflection (for example if Order class have OrderId key property you can try to determine it by getting class name with reflection and concatenating it with "Id" string).
I have the same problem:
public override TEntity Find(params object[] keyValues)
{
if (keyValues.Length == 0)
throw new ArgumentNullException("keyValues");
return this.SingleOrDefault(GenerateKeyFilter(keyValues));
}
private Expression<Func<TEntity, bool>> GenerateKeyFilter(object[] keyValues)
{
var conditions = new List<BinaryExpression>();
var objectParam = Expression.Parameter(typeof(TEntity));
var keyFields = !!!Helper.KeyFields<TEntity>();
if (keyFields.Count != keyValues.Length)
throw new KeyNotFoundException();
for (var c = 0; c < keyFields.Count; c++)
conditions.Add(Expression.MakeBinary(
ExpressionType.Equal,
Expression.MakeMemberAccess(objectParam, keyFields[c]),
Expression.Constant(keyValues[c], keyFields[c].PropertyType)
));
var result = conditions[0];
for (var n = 1; n < conditions.Count; n++)
result = Expression.And(result, conditions[n]);
return Expression.Lambda<Func<TEntity, bool>>(result, objectParam);
}
This works well, however, to create the tests I had to reconfigure the models and manually set the keys, similar to use of EntityTypeConfiguration.