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.
Related
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!
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 the following code:
public int LoadFilesAndSaveInDatabase(string filesPath)
{
var calls = new ConcurrentStack<GdsCallDto>();
var filesInDirectory = this._directoryProxy.GetFiles(filesPath);
if (filesInDirectory.Any())
{
Parallel.ForEach(filesInDirectory, file =>
{
var lines = this._fileProxy.ReadAllLines(file, Encoding.Unicode);
if (lines.Any())
{
// Reads the file and setup a new DTO.
var deserializedCall = this._fileManager.DeserializeFileContent(lines, Path.GetFileName(file));
// Insert the DTO in the database.
this._gdsCallsData.InsertOrUpdateGdsCall(deserializedCall);
// We keep track of the dto to count the number of restored items.
calls.Push(deserializedCall);
}
});
}
return calls.Count;
}
And I have the following unit test:
[TestMethod]
public void ShouldLoadFilesAndSaveInDatabase()
{
// Arrange
var path = RandomGenerator.GetRandomString(56);
var encoding = Encoding.Unicode;
var fileNameEnvironment = RandomGenerator.GetRandomString();
var fileNameModule = RandomGenerator.GetRandomString();
var fileNameRecordLocator = RandomGenerator.GetRandomString(6);
var fileNameTimestamp = RandomGenerator.GetRandomDateTime().ToString("O").Replace(':', 'o');
// We simulate the presence of 4 files.
var files = new List<string>
{
RandomGenerator.GetRandomString(255),
RandomGenerator.GetRandomString(255),
RandomGenerator.GetRandomString(255),
RandomGenerator.GetRandomString(255)
}.ToArray();
var expectedResult = 4;
this._directoryProxy.Expect(d => d.GetFiles(path))
.Return(files);
this._fileProxy.Expect(f => f.ReadAllLines(path, encoding))
.Return(files).Repeat.Times(files.Length);
// Act
var result = this._databaseReloadManager.LoadFilesAndSaveInDatabase(path);
// Assert
Assert.AreEqual(result, expectedResult);
this._directoryProxy.AssertWasCalled(d => d.GetFiles(path));
this._fileProxy.AssertWasCalled(f => f.ReadAllLines(path, Encoding.Unicode));
}
The problem is on the following line:
var lines = this._fileProxy.ReadAllLines(file, Encoding.Unicode);
Even though I set an expectation and a return value, when I run the unit test, it always returns null.
I am using Rhino.Mocks, it works perfectly fine elsewhere but not there.
I had a look at some discussions here but none of them helped. Can it be due to the use of Parallel.ForEach? Is there a way of doing such a mock?
If you need any other info, please let me know.
I don't think there is a problem with the Parallelization. It seems your problem is related to the proxy instance setup with Rhino Mock.
Ensure what you pass into the parameters of the ReadAllLines are the same as you call them when it run through the production code.
this._fileProxy.Expect(f => f.ReadAllLines(path, encoding))
.Return(files).Repeat.Times(files.Length);
For instance if you setup on different path and the value of that path diefferent when test executes you may see NULL in return. But again hard to tell without seeing the full setup/constructor in the code. Also check the random generators see what has been used in each time.
The below is I sort of put together and working for me.
Working means I don't get NULL for:
var lines = this._fileProxy.ReadAllLines(file, Encoding.Unicode);
//some dummy code so I can compile
public interface IProxyDir
{
IEnumerable<string> GetFiles(string path);
}
public class GdsCallDto
{
}
public class Proxy : IProxyDir
{
public IEnumerable<string> GetFiles(string path)
{
throw new NotImplementedException();
}
}
public interface IFileDir
{
IEnumerable<string> ReadAllLines(string path, Encoding encoding);
}
public class FileProxy : IFileDir
{
public IEnumerable<string> ReadAllLines(string path, Encoding encoding)
{
throw new NotImplementedException();
}
}
public interface IFileMgr
{
string DeserializeFileContent(IEnumerable<string> lines, string content);
}
public class FileMgr : IFileMgr
{
public string DeserializeFileContent(IEnumerable<string> lines, string content)
{
throw new NotImplementedException();
}
}
//system under test
public class Sut
{
private IProxyDir _directoryProxy;
private IFileDir _fileProxy;
private IFileMgr _fileManager;
public Sut(IProxyDir proxyDir, IFileDir fileProxy, IFileMgr mgr)
{
_fileManager = mgr;
_directoryProxy = proxyDir;
_fileProxy = fileProxy;
}
public int LoadFilesAndSaveInDatabase(string filesPath)
{
var calls = new ConcurrentStack<GdsCallDto>();
var filesInDirectory = this._directoryProxy.GetFiles(filesPath);
if (filesInDirectory.Any())
{
Parallel.ForEach(filesInDirectory, file =>
{
var lines = this._fileProxy.ReadAllLines("ssss", Encoding.Unicode);
if (lines.Any())
{
// Reads the file and setup a new DTO.
var deserializedCall = this._fileManager.DeserializeFileContent(lines, Path.GetFileName("file"));
// Insert the DTO in the database.
//this._gdsCallsData.InsertOrUpdateGdsCall(deserializedCall);
// We keep track of the dto to count the number of restored items.
//calls.Push(deserializedCall);
}
});
}
return 1;
}
}
Sample Unit Test
[TestClass]
public class UnitTest1
{
private IProxyDir _directoryProxy;
private IFileDir _fileProxy;
private IFileMgr _fileMgr;
private Sut _sut;
public UnitTest1()
{
_directoryProxy = MockRepository.GenerateMock<IProxyDir>();
_fileProxy = MockRepository.GenerateMock<IFileDir>();
_fileMgr = MockRepository.GenerateMock<IFileMgr>();
}
[TestMethod]
public void ShouldLoadFilesAndSaveInDatabase()
{
// Arrange
var path = RandomGenerator.GetRandomString(56);
var encoding = Encoding.Unicode;
var fileNameEnvironment = RandomGenerator.GetRandomString(5);
var fileNameModule = RandomGenerator.GetRandomString(5);
var fileNameRecordLocator = RandomGenerator.GetRandomString(6);
var fileNameTimestamp = RandomGenerator.GetRandomDateTime().ToString("O").Replace(':', 'o');
// We simulate the presence of 4 files.
var files = new List<string>
{
RandomGenerator.GetRandomString(255),
RandomGenerator.GetRandomString(255),
RandomGenerator.GetRandomString(255),
RandomGenerator.GetRandomString(255)
}.ToArray();
var expectedResult = 4;
this._directoryProxy.Expect(d => d.GetFiles(path))
.Return(files);
this._fileProxy.Expect(f => f.ReadAllLines(path, encoding))
.Return(files).Repeat.Times(files.Length);
_sut = new Sut(_directoryProxy, _fileProxy, _fileMgr);
// Act
var result = this._sut.LoadFilesAndSaveInDatabase(path);
// Assert
Assert.AreEqual(result, expectedResult);
this._directoryProxy.AssertWasCalled(d => d.GetFiles(path));
this._fileProxy.AssertWasCalled(f => f.ReadAllLines(path, Encoding.Unicode));
}
}
internal class RandomGenerator
{
public static string GetRandomString(int number)
{
return "ssss";
}
public static DateTime GetRandomDateTime()
{
return new DateTime();
}
}
I could get rid of this issue, probably caused by the use of random values. I am now calling the method IgnoreArguments() on my expectation:
this._fileProxy.Expect(f => f.ReadAllLines(path, encoding))
.Return(files).Repeat.Times(files.Length).IgnoreArguments();
It does the trick (i.e. the unit test is running successfully), but I do not know if it is very elegant.
I'm new to MEF. I'm wondering when to initialize instances through composition rather than calling their constructor. For example I'm not sure how to approach the following problem in MEF. If Manager class is in a another DLL and referenced in Program
class Program
{
static void Main(string[] args)
{
var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
var container = new CompositionContainer(catalog);
var manager= container.GetExportedValue<Manager>();
container.ComposeParts(manager);
var letter = manager.Letter;
Console.ReadKey();
}
}
//Manager Class is Business Access Layer referenced to Program as DLL
[Export]
public class Manager
{
private Letter _letter;
[Import]
public Letter Letter
{
get { return _letter ?? (_letter = InitializeComposeLetter()); }
}
private Letter InitializeComposeLetter()
{
var attachments = new List<string>();
var details = new StringBuilder();
var attachmentDirectory = ConfigurationManager.AppSettings["attachmentsDirectory"];
var letterPath = ConfigurationManager.AppSettings["letterPath"];
if (Directory.Exists(attachmentDirectory))
{
var files = Directory.EnumerateFiles(attachmentDirectory);
attachments = files.ToList();
}
if (File.Exists(letterPath))
{
var lines = File.ReadAllLines(letterPath);
foreach (var line in lines)
{
details.Append(line);
details.Append(Environment.NewLine);
}
}
//*********************QUESTION IS HERE *************//
var letter = new Letter() //SHOULD I MEF THIS OUT ??? IF SO HOW??
{
Subject = ConfigurationManager.AppSettings["defaultEmailTitle"],
Details = details.ToString(),
Attachments = attachments,
};
return letter;
}
}
Since you are not using auto-implemented properties, you should import the field instead of the property:
[Import]
private Letter _letter;
Note that if there is not part of type Letter, the composition of Manager will fail. If you want to handle it yourself (as is shown from your code sample) you can use the ImportAttribute.AllowDefault property:
[Import(AllowDefault = true)]
private Letter _letter;
Finally you do not need the call to ComposeParts. This is needed for objects that you have created. If the object is created by the container the composition is done behind the scenes.
So for objects that you create:
var manager = new Manager();
container.ComposeParts(manager);
otherwise:
var manager= container.GetExportedValue<Manager>();
Note that GetExportedValue will throw if the composition fails. There are other GetExportXXX methods that will not throw. Choose one depending on your needs.
This is my interface
public interface IWork
{
string GetIdentifierForItem(Information information);
}
and my class
public class A : IWork
{
[ImportMany]
public IEnumerable<Lazy<IWindowType, IWindowTypeInfo>> WindowTypes { get; set; }
public string GetIdentifierForItem(Information information)
{
string identifier = null;
string name = information.TargetName;
// Iterating through the Windowtypes
// searching the 'Name' and then return its ID
foreach (var windowType in WindowTypes)
{
if (name == windowType.Metadata.Name)
{
identifier = windowType.Metadata.UniqueID;
break;
}
}
return identifier;
}
}
Problem : I want to unit test the method GetIdentifierForItem
Here is what I tried doing to solve it -
(1)Create a mock Lazy and set the values that it needs to return on property gets
var windowMock = new Mock<Lazy<IWindowType, IWindowTypeInfo>>();
windowMock.Setup(foo => foo.Metadata.Name).Returns("Data");
windowMock.Setup(foo => foo.Metadata.UniqueID).Returns("someString");
(2)Create a window type list and the above mocked object and then set it to the created A object
var WindowTypesList = new List<IWindowType, IWindowTypeInfo>>();
WindowTypesList.Add(windowMock.Object);
A a = new A();
a.WindowTypes = WindowTypesList;
(3) Create the information mock
var InfoMock = new Mock<Information>();
InfoMock.Setup(foo => foo.TargetName).Returns("Data");
To put all of the above together as the unit test
[TestMethod]
public void GetIDTest()
{
var windowMock = new Mock<Lazy<IWindowType, IWindowTypeInfo>>();
windowMock.Setup(foo => foo.Metadata.Name).Returns("Data");
windowMock.Setup(foo => foo.Metadata.UniqueID).Returns("someString");
var WindowTypesList = new List<Lazy<IWindowType, IWindowTypeInfo>>();
WindowTypesList.Add(windowMock.Object);
A a = new A();
a.WindowTypes = WindowTypesList;
var InfoMock = new Mock<Information>();
InfoMock.Setup(foo => foo.TargetName).Returns("Data");
string expected = "someString"; // TODO: Initialize to an appropriate value
string actual;
actual = a.GetIdentifierForItem(InfoMock.Object);
Assert.AreEqual(expected, actual);
}
This unit test fails to execute and throws an exception 'TargetInvocationException' and veiwing the detail, it looks like I am doing something that I should not be doing.
But I am not sure how do it other way. I have read some of the links in the Quickstart guide of Moq. I know I am missing something. Can you help me by guiding how to unit test this?
This is how it may be done, after setting up the mocks
1) Creating a CompositionContainer, that holds the imports.
2) Adding Mocks to the container.
container.ComposeExportedValue(mock.Object);
3) Create an instance of tested class
4) Compose mocks to the import
container.ComposeParts(instance);
You don't need to Mock the Lazy<T,TMetadta>. It is flexible enough to work with your test. Instead, Mock the IWindowTypeInfo
[TestMethod]
public void GetIDTest()
{
var windowTypeInfoMock = new Mock<IWindowTypeInfo>();
windowTypeInfoMock.Setup(foo => foo.Name).Returns("Data");
windowTypeInfoMock.Setup(foo => foo.UniqueID).Returns("someString");
var lazyWindow =
new Lazy<IWindowType, IWindowTypeInfo>(windowTypeInfoMock.Object);
var WindowTypesList = new List<Lazy<IWindowType, IWindowTypeInfo>>();
WindowTypesList.Add(lazyWindow);
var a = new A();
a.WindowTypes = WindowTypesList;
var InfoMock = new Mock<Information>();
InfoMock.Setup(foo => foo.TargetName).Returns("Data");
string expected = "someString";
string actual;
actual = a.GetIdentifierForItem(InfoMock.Object);
Assert.AreEqual(expected, actual);
}
Your test passes on my machine with only small modifications, you do not need to use a composition container for this test.