I’m still a beginner in C# and unit testing and I have a problem that I don’t know how to solve.
I've a Mongo Database and I just want to mock this database.
I’ll start by showing you my code before I explain my problem.
Here is my unit test class:
private FollowManager _followManager;
private FollowsDao _followSubstitute;
private UsersDao _userSubstitute;
private IMongoDatabase _database;
private IMongoCollection<Follow> _followMongoCollection;
private IMongoCollection<User> _userMongoCollection;
[OneTimeSetUp]
public void OneTimeSetup()
{
_database = Substitute.For<IMongoDatabase>();
_followMongoCollection = Substitute.For<IMongoCollection<Follow>>();
_userMongoCollection = Substitute.For<IMongoCollection<User>>();
_followSubstitute = new FollowsDao(_database, _followMongoCollection);
_userSubstitute = new UsersDao(_database, _userMongoCollection);
_followManager = new FollowManager(_followSubstitute, _userSubstitute);
}
[TestCase(1, 2)]
[TestCase(3, 4)]
public void AssertThatBuildFollowReturnsValidFollow(int followerId, int followedId)
{
UserFollow userFollow = new UserFollow {Follower = followerId, Followed = followedId};
Follow follow = _followManager.BuildFollow(userFollow);
Assert.IsNotNull(follow);
}
Here is my function that I want to test in my FollowManager:
public Follow BuildFollow(UserFollow follow)
{
User follower = _userDao.FindUserByNumber(follow.Follower).Result;
User followed = _userDao.FindUserByNumber(follow.Followed).Result;
Follow newFollow = new Follow { Follower = follower, Followed = followed, Date = DateTime.Now };
InsertOrUpdateFollow(newFollow);
return newFollow;
}
The FindUserByNumber method will search in the database for a user that matches the id passed as a parameter:
public async Task<User> FindUserByNumber(int number)
{
List<User> result = await _collection.Find(user => user.Number == number && user.Active).ToListAsync();
return result.FirstOrDefault();
}
My problem is:
When I call Follow follow = _followManager.BuildFollow(userFollow); in my unit test, this line InsertOrUpdateFollow(newFollow); in my manager throw an error. It's normal because properties Follower and Followed are null because my database is mocked.
I have a JSON file that contains the list of users of the database and I want to be able to recover my users from this file and not from the database.
With this, I've my users in my JSON file:
List<User> users = JsonConvert.DeserializeObject<List<User>>(File.ReadAllText(#"C:\users-list-api-result.json"));
So, my question is:
How can I test this function with JSON mocked file ?
Thanks in advance!
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 using Microsoft Graph Api client and performing add members to group.
Docs here :- https://learn.microsoft.com/en-us/graph/api/group-post-members?view=graph-rest-1.0&tabs=csharp#example-2-add-multiple-members-to-a-group-in-a-single-request
I have successfully achieved the requirement. But when come to write test for my service class having no clue what and how to verify it.
I am beginner to the API dev and also with Microsoft Graph API. Below are my codes, Take a look and post your suggestions and comments. It might be helpful.
Service Class:
public class UserGroupService : IUserGroupService
{
private readonly IGraphServiceClient _graphServiceClient;
public UserGroupService(IGraphServiceClient graphServiceClient)
{
_graphServiceClient = graphServiceClient;
}
public async Task AddAsync(string groupId, IList<string> userIds)
{
var group = new Group
{
AdditionalData = new Dictionary<string, object>()
{
{"members#odata.bind", userIds.Select(x => $"https://graph.microsoft.com/v1.0/directoryObjects/{x}") }
}
};
await _graphServiceClient.Groups[groupId].Request().UpdateAsync(group);
}
}
ServiceTest :
public class UserGroupServiceTests
{
private readonly Fixture _fixture = new Fixture();
private readonly Mock<IGraphServiceClient> _graphServiceClientMock = new Mock<IGraphServiceClient>();
private readonly IUserGroupService _userGroupService;
public UserGroupServiceTests()
{
_userGroupService = new UserGroupService(_graphServiceClientMock.Object);
}
// Settingup GraphClientMock
private void SetupGraphClientMock(string groupId, IList<string> userIds, Group group)
{
var groupRequest = new Mock<IGroupRequest>();
var groupRequestBuilder = new Mock<IGroupRequestBuilder>();
groupRequest.Setup(x => x.UpdateAsync(group));
groupRequestBuilder.Setup(x => x.Request()).Returns(groupRequest.Object);
_graphServiceClientMock.Setup(x => x.Groups[groupId]).Returns(groupRequestBuilder.Object);
}
[Fact]
public async Task AddAsync_GivenValidInput_WhenServiceSuccessful_AddAsyncCalledOnce()
{
object result;
var groupId = _fixture.Create<string>();
var userIds = _fixture.Create<IList<string>>();
var dictionary = _fixture.Create<Dictionary<string, object>>();
dictionary.Add("members#odata.bind", userIds.Select(x => $"https://graph.microsoft.com/v1.0/directoryObjects/{x}"));
var group = _fixture.Build<Group>().With(s => s.AdditionalData, dictionary).OmitAutoProperties().Create();
SetupGraphClientMock(groupId, userIds, group);
await _userGroupService.AddAsync(groupId, userIds);
//TODO
// Need to verify _graphServiceClientMock AdditionalData value == mocking group AdditionalData value which is called once in _graphServiceClientMock.
// Below implementation done using TryGetValue which return bool, I am really afraid to write test using bool value and compare and I feel its not a right way to write test.
_graphServiceClientMock.Verify(m => m.Groups[groupId].Request().UpdateAsync(It.Is<Group>(x => x.AdditionalData.TryGetValue("members#odata.bind", out result) == group.AdditionalData.TryGetValue("members#odata.bind", out result))), Times.Once);
_graphServiceClientMock.VerifyNoOtherCalls();
}
}
I wants to verify _graphServiceClientMock AdditionalData value == mocking group AdditionalData value which is called once in _graphServiceClientMock like the above. Anyone have idea on this. Please post your comments.Thanks in Advance.
Based on the subject under test and the simplicity of the provided member under test the following example demonstrates how it can be tested in isolation,
public class UserGroupServiceTests {
[Fact]
public async Task AddAsync_GivenValidInput_WhenServiceSuccessful_AddAsyncCalledOnce() {
//Arrange
string groupId = "123456";
IList<string> userIds = new[] { "a", "b", "c" }.ToList();
string expectedKey = "members#odata.bind";
IEnumerable<string> expectedValues = userIds
.Select(x => $"https://graph.microsoft.com/v1.0/directoryObjects/{x}");
Group group = null;
Mock<IGraphServiceClient> clientMock = new Mock<IGraphServiceClient>();
clientMock
.Setup(x => x.Groups[groupId].Request().UpdateAsync(It.IsAny<Group>()))
.Callback((Group g) => group = g) //Capture passed group for assertion later
.ReturnsAsync(group) //To allow async flow
.Verifiable();
IUserGroupService _userGroupService = new UserGroupService(clientMock.Object);
//Act
await _userGroupService.AddAsync(groupId, userIds);
//Assert
clientMock.Verify(); //have verifiable expressions been met
clientMock.VerifyNoOtherCalls();
//Using FluentAssertions to assert captured group
group.Should().NotBeNull();//was a group passed
group.AdditionalData.Should().NotBeNull()// did it have data
.And.ContainKey(expectedKey);//and did the data have expected key
(group.AdditionalData[expectedKey] as IEnumerable<string>)
.Should().BeEquivalentTo(expectedValues);//are values as expected
}
}
Review the code comments to get an understanding of how the test was exercised to verify the expected behavior.
The intuitive nature of the used FluentAssertions should also help in understanding what is being asserted
I'm learning to write unit tests in xUnit and Moq, I have a problem somewhat. I wrote 2 tests in one, I add a category and download all, checking through Assert or whatever they are. In the second case, I also add categories, and I get the details of the added category, unfortunately I can not display the details of the downloaded category, it's the TestCategoryDetails test. What am I doing wrong?
using Moq;
using relationship.Models;
using Xunit;
using Xunit.Abstractions;
namespace Testy
{
public class UnitTest1
{
private readonly ITestOutputHelper _output;
public UnitTest1(ITestOutputHelper output)
{
_output = output;
}
[Fact]
public void TestCategoryList()
{
var categoryMock = new Mock<ICategoryRepository>();
var contextMock = new Mock<AppDbContext>();
categoryMock.Setup(x => x.AddCategory(new GameCategory { Id= 1, Name = "Tester" }));
var result = categoryMock.Object;
Assert.NotNull(result.GameCategory());
}
[Fact]
public void TestCategoryDetails()
{
var categoryMock = new Mock<ICategoryRepository>();
var contextMock = new Mock<AppDbContext>();
categoryMock.Setup(x => x.AddCategory(new GameCategory { Id = 1, Name = "Tester" }));
var result = categoryMock.Object;
var categoryDetails = result.GetDetails(1);
Assert.NotNull(categoryDetails);
}
}
}
In general, I wanted to test my repository by checking how add, edit, delete, download all categories and details of the selected one, unfortunately I'm not doing anything.
What are you doing is you are trying to test the mockup of the repository abstraction. But you want to test your implementation.
What works well to test with db context is to use in memory provider for the real context. For the details see:
https://learn.microsoft.com/en-us/ef/core/miscellaneous/testing/
At the end it may look like this (second test):
...
[Fact]
public void TestCategoryDetails()
{
// arrange
var categoryRepository = new CategoryRepository(GetContextWithInMemoryProvider());
// act
categoryRepository.AddCategory(new GameCategory { Id = 1, Name = "Tester" });
var categoryDetails = categoryRepository.GetDetails(1);
// assert
Assert.NotNull(categoryDetails);
}
private AppDbContext GetContextWithInMemoryProvider()
{
// create and configure context
// see: https://learn.microsoft.com/en-us/ef/core/miscellaneous/testing/
}
...
I have a method I am attempting to Unit Test which makes use of HttpContext.Current.Server.MapPath as well as File.ReadAllLines as follows:
public List<ProductItem> GetAllProductsFromCSV()
{
var productFilePath = HttpContext.Current.Server.MapPath(#"~/CSV/products.csv");
String[] csvData = File.ReadAllLines(productFilePath);
List<ProductItem> result = new List<ProductItem>();
foreach (string csvrow in csvData)
{
var fields = csvrow.Split(',');
ProductItem prod = new ProductItem()
{
ID = Convert.ToInt32(fields[0]),
Description = fields[1],
Item = fields[2][0],
Price = Convert.ToDecimal(fields[3]),
ImagePath = fields[4],
Barcode = fields[5]
};
result.Add(prod);
}
return result;
}
I have a Unit Test setup which (as expected) fails:
[TestMethod()]
public void ProductCSVfileReturnsResult()
{
ProductsCSV productCSV = new ProductsCSV();
List<ProductItem> result = productCSV.GetAllProductsFromCSV();
Assert.IsNotNull(result);
}
I have since done a lot of reading on Moq and Dependancy Injection which I just dont seem to be able to implement. I have also seen a few handy answers on SO such as: How to avoid HttpContext.Server.MapPath for Unit Testing Purposes however I am just unable to follow it for my actual example.
I am hoping someone is able to take a look at this and tell me exactly how I might go about implementing a successful test for this method. I feel I have a lot of the background required but am unable to pull it all together.
In its current form, the method in question is too tightly coupled to implementation concerns that are difficult to replicate when testing in isolation.
For your example, I would advise abstracting all those implementation concerns out into its own service.
public interface IProductsCsvReader {
public string[] ReadAllLines(string virtualPath);
}
And explicitly inject that as a dependency into the class in question
public class ProductsCSV {
private readonly IProductsCsvReader reader;
public ProductsCSV(IProductsCsvReader reader) {
this.reader = reader;
}
public List<ProductItem> GetAllProductsFromCSV() {
var productFilePath = #"~/CSV/products.csv";
var csvData = reader.ReadAllLines(productFilePath);
var result = parseProducts(csvData);
return result;
}
//This method could also eventually be extracted out into its own service
private List<ProductItem> parseProducts(String[] csvData) {
List<ProductItem> result = new List<ProductItem>();
//The following parsing can be improved via a proper
//3rd party csv library but that is out of scope
//for this question.
foreach (string csvrow in csvData) {
var fields = csvrow.Split(',');
ProductItem prod = new ProductItem() {
ID = Convert.ToInt32(fields[0]),
Description = fields[1],
Item = fields[2][0],
Price = Convert.ToDecimal(fields[3]),
ImagePath = fields[4],
Barcode = fields[5]
};
result.Add(prod);
}
return result;
}
}
Note how the class now is not concerned with where or how it gets the data. Only that it gets the data when asked.
This could be simplified even further but that is outside of the scope of this question. (Read up on SOLID principles)
Now you have the flexibility to mock the dependency for testing at a high level, expected behavior.
[TestMethod()]
public void ProductCSVfileReturnsResult() {
var csvData = new string[] {
"1,description1,Item,2.50,SomePath,BARCODE",
"2,description2,Item,2.50,SomePath,BARCODE",
"3,description3,Item,2.50,SomePath,BARCODE",
};
var mock = new Mock<IProductsCsvReader>();
mock.Setup(_ => _.ReadAllLines(It.IsAny<string>())).Returns(csvData);
ProductsCSV productCSV = new ProductsCSV(mock.Object);
List<ProductItem> result = productCSV.GetAllProductsFromCSV();
Assert.IsNotNull(result);
Assert.AreEqual(csvData.Length, result.Count);
}
For completeness here is what a production version of the dependency could look like.
public class DefaultProductsCsvReader : IProductsCsvReader {
public string[] ReadAllLines(string virtualPath) {
var productFilePath = HttpContext.Current.Server.MapPath(virtualPath);
String[] csvData = File.ReadAllLines(productFilePath);
return csvData;
}
}
Using DI just make sure that the abstraction and the implementation are registered with the composition root.
The use of HttpContext.Current makes you assume that the productFilePath is runtime data, but in fact it isn't. It is configuration value, because it _will not change during the lifetime of the application. You should instead inject this value into the constructor of the component that needs it.
That will obviously be a problem in case you use HttpContext.Current, but you can call HostingEnvironment.MapPath() instead; no HttpContext is required:
public class ProductReader
{
private readonly string path;
public ProductReader(string path) {
this.path = path;
}
public List<ProductItem> GetAllProductsFromCSV() { ... }
}
You can construct your class as follows:
string productCsvPath = HostingEnvironment.MapPath(#"~/CSV/products.csv");
var reader = new ProductReader(productCsvPath);
This doesn't solve the tight coupling with File, but I'll refer to Nkosi's excellent answer for the rest.
I am working with AIMLbot.dll in c#. I saw two functions saveToBinaryFile and loadFromBinaryFile. I think these functions are to store current contents in bot's brain to a file. But it doesn't seems to be working! Means, If I say to remember my name and save the content to GraphMaster.dat file. Next time I load the content of the same file and when I ask my name its giving the wrong answer. My class is as follows.
class AIBot
{
private Bot myBot;
private User myUser;
public AIBot()
{
myBot = new Bot();
myUser = new User("UnknownUser", myBot);
}
public void Initialize()
{
myBot.loadSettings();
myBot.isAcceptingUserInput = false;
myBot.loadAIMLFromFiles();
myBot.isAcceptingUserInput = true;
}
public void Load()
{
if (File.Exists(AppDomain.CurrentDomain.BaseDirectory + #"\Graphmaster.dat"))
myBot.loadFromBinaryFile(AppDomain.CurrentDomain.BaseDirectory + #"\Graphmaster.dat");
}
public string GetResponse(string input)
{
Request r = new Request(input, myUser, myBot);
Result res = myBot.Chat(r);
return (res.Output);
}
public void Save()
{
myBot.saveToBinaryFile(AppDomain.CurrentDomain.BaseDirectory + #"\Graphmaster.dat");
}
}
Can anybody help to point out the problem?
I got a solution for the problem. Hope it will help others.
Save user session like
this.myUser.Predicates.DictionaryAsXML.Save(saveFileDialogDump.FileName);
Load the stored session next time.
this.myUser.Predicates.loadSettings(openFileDialogDump.FileName);