So the scenario is this: a user does some action (like earn a badge or unlock something) and an email notification gets sent out. One to the user (with a message like "You've unlocked XYZ...") and then a different message to each of their friends like ("You're friend has unlocked XYZ...").
public interface INotify
{
void Notify(User user, User friend);
}
public class NotificationService
{
private IEnumerable<INotify> _notifiers;
public NotificationService(IEnumerable<INotify> notifiers)
{
_notifiers = notifiers;
}
public SendNotifications()
{
User user = GetUser();
IEnumerable<User> friends = GetFriends();
foreach(var notifier in _notifiers)
{
//Send notification to user
notifier.Notify(user, null);
//send notification to users friends
foreach(var friend in friends)
notifier.Notify(user, friend);
}
}
}
I'm trying to use moq to test that each notifier is called 2x. Once passing null as the second parameter and the second time passing in a value to both parameters.
[Test]
public void MakeSureEveryoneIsNotified()
{
var notifierMock = new Mock<INotifier>();
var svc = new NotificationService(new List<INotifier>{ notifierMock.Object });
svc.SendNotifications();
notifierMock.Verify(x => x.Notify(It.Is<User>(user => user.UserId == 1), null), Times.Once());
notifierMock.Verify(x => x.Notify(It.Is<User>(user => user.UserId == 1), It.Is<User>(user => user.UserId == 2)), Times.Once());
}
The problem is that the second verify call throws an ArgumentNullException for the second parameter. Is there away to say "Check the first call has these parameters, and then the second call has other parameters". I know I can get it around it simply by calling:
notifierMock.Verify(x => x.Notify(It.IsAny<User>(), It.IsAny<User>()), Times.Exactly(2));
But I was wanting to be a little more specific. Anyway to do this?
You can achieve this by recording what happens on each call to Notify. Then you can compare the recording to what's expected:
[TestMethod]
public void TestMoqInvocations()
{
var notifierMock = new Mock<INotifier>();
var svc = new NotificationService(new List<INotifier>{ notifierMock.Object });
svc.SendNotifications();
var invocations = new List<NotifyParams>();
notifierMock
.Setup(f => f.Notify(It.IsAny<User>(), It.IsAny<User>()))
.Callback<string, string>((user, friend) => invocations.Add(new NotifyParams{user = user, friend = friend}));
Assert.AreEqual(1, invocations[0].user.UserId);
Assert.IsNull(invocations[0].friend);
Assert.AreEqual(1, invocations[1].user.UserId);
Assert.AreEqual(2, invocations[1].user.UserId);
}
public struct NotifyParams {
public User user {get;set;}
public User friend { get; set; }
}
You can create a method for get User list. Then get user by It.Is method.
private bool GetUser(User user, List<User> users)
{
if (user != null)
users.Add(user);
return true;
}
[Test]
public void MakeSureEveryoneIsNotified()
{
var notifierMock = new Mock<INotifier>();
var svc = new NotificationService(new List<INotifier>{ notifierMock.Object });
svc.SendNotifications();
var users = new List<User>();
var friends = new List<User>();
// verify how many times call the method
notifierMock
.Verify(x => x.Notify(
It.Is<User>(u => GetUser(u, users)),
It.Is<User>(f => GetFriend(f, friends))
), Times.Exactly(2));
Assert.AreEquals(2, users.Count);
Assert.AreEquals(1, users[0].UserId);
Assert.AreEquals(1, users[1].UserId);
Assert.AreEquals(1, friends.Count);
Assert.AreEquals(2, friends[0].UserId);
}
Related
I am trying to unit test a method that returns a list of users with mocking by creating a list of users and passing them in to mock whether my repository methods returns that list of users.
At the moment I am getting an empty list. I am wondering if there's something missing when passing in the users in the test method.
I have a repository class with a method called GetUsersOnly() which makes a call to the Graph API like this:
this.graphApi.GetUsersAsync().Result;
This GetUsersAsync() is inside of the GraphAPI class that I wrote and makes a call to the Microsoft Graph API to get a list of users.
public Task<IGraphServiceUsersCollectionPage> GetUsersAsync()
{
return this.appClient.Users
.Request(this.queryOptions)
.Select(#$"
Id,
DisplayName,
GivenName,
Surname,
Mail,
OtherMails,
CompanyName,"
.OrderBy("DisplayName")
.GetAsync();
}
public class B2CRepository : IB2CRepository
{
private readonly IGraphAPI graphApi;
public B2CRepository(IGraphAPI graphApi)
{
this.graphApi = graphApi;
}
private List<User> GetUsersOnly()
{
var request = this.graphApi.GetUsersAsync().Result;
List<User> users = new List<User>();
var graphUsers = request.ToList();
do
{
users.AddRange(graphUsers);
var nextPage = request.NextPageRequest;
graphUsers = nextPage?.GetAsync().Result.ToList() ?? new List<User>();
}
while (graphUsers.Count > 0);
return users;
}
}
Inside my test I am mocking the GraphAPI class:
var mockGraphAPI = new Mock<IGraphAPI>();
Test method looks like this: my goal is simply to pass in some list of users and return that list from my repo method GetUsersOnly();
[TestMethod]
public void GetUserTest()
{
var users = new List<User>();
var mockGraphAPI = new Mock<IGraphAPI>();
for (int i = 0; i < 3; i++)
{
var user = new User()
{
DisplayName = "TestUser2" + i,
};
users.Add(user);
}
var mockUserPageResult = new Mock<IGraphServiceUsersCollectionPage>();
mockGraphAPI.Setup(api =>
api.GetUsersAsync()).ReturnsAsync(mockUserPageResult.Object);
mockUserPageResult.SetupGet(page => page.NextPageRequest);
this.b2CRepository = new B2CRepository(mockGraphAPI.Object);
var usersResult = this.b2CRepository.GetUsers();
Assert.IsTrue(usersResult.Count() == 2);
}
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 would like to add logic to check if the agent name is already created and alert the user. I created this IQueryable SearchAgents which takes in a string query and I was going to add it to the controller but I am not sure if this is the correct way.
Is this the correct path for validating an agent is already in the system?
AgentController
[HttpPost]
[ApplicationApiAuthorize("Administrator, ContentManager")]
public IHttpActionResult CreateAgent([FromBody]AgentModel agentModel)
{
LogHelper.Info($"Creating agent {agentModel.Name}");
//Search if Agent name is in the system
var AgentId = AgentsDataService.SearchAgents.Select(a => new AgentModel {Name = agentModel.Name }).ToList();
var agentEntity = new Agent();
Mapper.DynamicMap(agentModel, agentEntity);
var agentInformationEntities = Mapper.Map<IEnumerable<AgentInformation>>(agentModel.AgentInformations);
agentEntity.AgentInformations = new EntitySet<AgentInformation>();
agentEntity.AgentInformations.AddRange(agentInformationEntities);
var operationResult = AgentsDataService.InsertAgent(agentEntity);
var result = Ok(new
{
Value = Mapper.Map<AgentModel>(operationResult)
});
return result;
}
AgentDataService
public IQueryable<Agent> SearchAgents(string query)
{
return GetAllAgents().Where(a => a.Name.Contains(query)).OrderBy(a => a.Name);
}
Personally since you only want to know if the entity exists in the database or not I would have the following method in the AgentDataService
public bool AgentExists(string agentName)
{
return GetAllAgents().Any(x => x.Name.Equals(agentName, StringComparison.InvariantCultureIgnoreCase));
}
or the async version (Assuming you are using Entity Framework):
public async Task<bool> AgentExistsAsync(string agentName)
{
return await GetAllAgents().AnyAsync(x => x.Name.Equals(agentName, StringComparison.InvariantCultureIgnoreCase));
}
Then you can call this from your controller and inform the user if the agent exists.
I have this logic in several methods in my MVC5 project. It works but I am repeating myself pretty consistently.
private PersonnelManagementEntities db = new PersonnelManagementEntities();
private ActiveDirectoryTools adt = new ActiveDirectoryTools();
private ManagerService ms = new ManagerService();
private UserService us = new UserService();
private CompanyService cs = new CompanyService();
public ActionResult CompanySummary(int id = 0)
{
//Repeating logic begins here
int setID = 0;
adt.GetUserInfo(User.Identity.Name);
//Fetch current users company
User currentUser = us.getUser(adt.adUserName);
//Determine if user is a global manager or not. If global display all companies
ViewBag.Company = cs.FetchCompanies(currentUser);
//You can only see the companies you're assigned to, in the AllRequests window. Unless manually overwritten in the URL
if (currentUser.GlobalUser == true && id > 0)
{
setID = id;
}
else
{
setID = (int)currentUser.CompanyID;
}
//End of repeating logic
var resultSet = db.UserTimeSummaryUpdated(setID);
return View(resultSet.ToList());
}
What do you guys think would be the best way of reducing the amount of times I repeat this?
You can see here in another method where I reuse this code:
public ActionResult AllRequests(int id = 0)
{
int setID = 0;
adt.GetUserInfo(User.Identity.Name);
User currentUser = us.getUser(adt.adUserName);
ViewBag.Company = cs.FetchCompanies(currentUser);
//You can only see the companies you're assigned to, in the AllRequests window. Unless manually overwritten in the URL
if (id > 0)
{
setID = id;
}
else
{
setID = (int)currentUser.CompanyID;
}
ViewBag.EmployeeList = db.Users
.Where(x => x.disabled == false)
.Where(x => x.CompanyID == setID)
.OrderBy(x => x.FullName)
.ToList();
IQueryable timeRequests = db.TimeRequests
.Include(t => t.ApproveDenyReason)
.Include(t => t.DayType)
.Include(t => t.User)
.Include(t => t.User1)
.Include(t => t.User2)
.OrderByDescending(t => t.sDateTime)
.Where(t => t.User.CompanyID == setID);
return View(timeRequests);
}
I was thinking about creating an ActionFilter and doing it that way but it seems kind of a hack instead of the correct way of doing things.
I also entertained the idea of when a user logs in I create a user object and persist it through a session.
Any help is appreciated
One option is to write a CustomController that inherits Controller. I did this to add Member Session Data and a Messaging Output System that can write to my LayoutView. For the example below I assumed FetchCompanies returns a list...
public class CustomController : Controller
{
private ActiveDirectoryTools _adt = new ActiveDirectoryTools();
private UserService _us = new UserService();
private CompanyService _cs = new CompanyService();
public List<Company> UserCompanies;
public ApplicationController()
: base()
{
_adt.GetUserInfo(User.Identity.Name);
User currentUser = _us.getUser(adt.adUserName);
UserCompanies = _cs.FetchCompanies(currentUser);
}
}
When you create your Controller inherit from this CustomController. Then in your ActionResult simply set using UserCompanies.
public AccountController:CustomController
{
public ActionResult Index()
{
ViewBag.Company = UserCompanies;
return View();
}
}
I would like to set up a method with Moq twice but it seems that the last one overrides the previous ones. Here's my initial setup:
string username = "foo";
string password = "bar";
var principal = new GenericPrincipal(
new GenericIdentity(username),
new[] { "Admin" });
var membershipServiceMock = new Mock<IMembershipService>();
membershipServiceMock.Setup(ms =>
ms.ValidateUser(username, password)
).Returns(new ValidUserContext {
Principal = principal
});
This works out fine but I want this to return new ValidUserContext() if the username or password is different to the username and password variables as above. To do that, I added another setup but this time it overrides the above one and always applies it:
membershipServiceMock.Setup(ms =>
ms.ValidateUser(It.IsAny<string>(), It.IsAny<string>())
).Returns(
new ValidUserContext()
);
What is the most elegant way of handling this type of situation with Moq?
Edit
I solved the problem with the below approach but I guess there is a better way of handling this:
var membershipServiceMock = new Mock<IMembershipService>();
membershipServiceMock.Setup(ms =>
ms.ValidateUser(It.IsAny<string>(), It.IsAny<string>())
).Returns<string, string>((u, p) =>
(u == username && p == password) ?
new ValidUserContext {
Principal = principal
}
: new ValidUserContext()
);
Moq supports this out of box with argument constraints:
mock.Setup(ms => ms.ValidateUser(
It.Is<string>(u => u == username), It.Is<string>(p => p == password))
.Returns(new ValidUserContext { Principal = principal });
mock.Setup(ms => ms.ValidateUser(
It.Is<string>(u => u != username), It.Is<string>(p => p != password))
.Returns(new ValidUserContext());
Catch-all It.IsAny also works, but the order is important:
// general constraint first so that it doesn't overwrite more specific ones
mock.Setup(ms => ms.ValidateUser(
It.IsAny<string>(), It.IsAny<string>())
.Returns(new ValidUserContext());
mock.Setup(ms => ms.ValidateUser(
It.Is<string>(u => u == username), It.Is<string>(p => p == password))
.Returns(new ValidUserContext { Principal = principal });
If you look at the function definition for Setup():
// Remarks:
// If more than one setup is specified for the same method or property, the latest
// one wins and is the one that will be executed.
public ISetup<T, TResult> Setup<TResult>(Expression<Func<T, TResult>> expression);
All you need to do is switch the order of the two Setup() calls:
membershipServiceMock.Setup(ms =>
ms.ValidateUser(It.IsAny<string>(), It.IsAny<string>())
).Returns(
new ValidUserContext()
);
membershipServiceMock.Setup(ms =>
ms.ValidateUser(username, password)
).Returns(new ValidUserContext {
Principal = principal
});
so if the input is indeed username and password, both Setup() calls are qualified but the later one wins because of the rule and when you have any other inputs, only the first one is matched and applied.
Another out-of-the-box option is to use the Return<> version to return different ValidUserContexts depending upon the parameters. It is not better than the above answer, just another option.
We set up ValidateUser() to return the result of a function GetUserContext(string, string), passing in the username and password with which ValidateUser() was called.
[TestClass]
public class MultipleReturnValues {
public class ValidUserContext {
public string Principal { get; set; }
}
public interface IMembershipService {
ValidUserContext ValidateUser(string name, string password);
}
[TestMethod]
public void DifferentPricipals() {
var mock = new Mock<IMembershipService>();
mock.Setup(mk => mk.ValidateUser(It.IsAny<string>(), It.IsAny<string>())).Returns<string, string>(GetUserContext);
var validUserContext = mock.Object.ValidateUser("abc", "cde");
Assert.IsNull(validUserContext.Principal);
validUserContext = mock.Object.ValidateUser("foo", "bar");
Assert.AreEqual(sPrincipal, validUserContext.Principal);
}
private static string sPrincipal = "A Principal";
private static ValidUserContext GetUserContext(string name, string password) {
var ret = new ValidUserContext();
if (name == "foo" && password == "bar") {
ret = new ValidUserContext { Principal = sPrincipal };
}
return ret;
}
}