I am writing the unit tests for my class library, There is a method for which I am trying to write tests. this method makes some database calls to get data from the database and inserts data into some tables. I want this to be fake. So it should like it is doing on actual database tables but actually it should not affect the original database.
I have not done this before, But I have tried the below way.
private Mock<DBService> _dBService;
MyDLL _myDll;
public UnitTest1()
{
_dBService = new Mock<DBService>();
_myDll= new MyDLL (_dBService.Object);
}
[TestMethod]
public void TestMethod1()
{
var response = _myDll.TestMethod(data);
...
}
public string TestMethod(List<long> data)
{
var temp = _dbService.GetDataFromDB(data);
...
...
_dbService.InsertIntoTable(someData);
}
I have used MOQ to fake the DBService
, because all the database-related methods are implemented in the DBService
class.
Also, If I try to get data from the database by directly using the _dbService
in the test, it returns null
. but it works as expected when it is called inside the TestMethod
.
[TestMethod]
public void TestMethod1()
{
var response = _dbService.GetDataFromDB(data); //returns null ?? why?
...
}
Update : Adding definition of GetDataFromDB
public List<Transaction> GetDataFromDB(List<long> ids)
{
XmlSerializer xmlSerializer = new XmlSerializer(ids.GetType());
using (StringWriter textWriter = new StringWriter())
{
xmlSerializer.Serialize(textWriter, ids);
string xmlId = textWriter.ToString();
var parameters = new[]
{
new SqlParameter("@xml", DbType.String) { Value = xmlId }
};
return _dataAccess
.CallProcedure<Transaction>("GetTransactionDetails", parameters).ToList();
}
}
public class Transaction
{
public long ID { get; set; }
public double? Amount { get; set; }
public long? CompanyId { get; set; }
}
I'm didn't understand what you want to test with your code, but to solve your problem you need to do this:
First you need to implement an interface in your DBService:
class DBService: IDBService
Of course you need to declare the public methods of DBService in this interface.
Then you mock the interface instead concrete class:
_dbService = Mock<IDBService>();
Now you can setup your methods.
To solve the first problem (inserting in real database) you setup your method and then call it's mock:
_dbService.Setup(x => x.InsertIntoTable(it...));
_dbService.Object.InsertTable(...);
To solve the second problem, you should use .Returns()
method passing your Mock result
_dbService.Setup(x => x.GetDataFromDB(It.IsAny<List<long>>)).Returns(YourMockedResult);
_dbService.Object.GetDataFromDB(...);
If you're any doubt about the implementation, here in docs have a lot of examples:
https://github.com/Moq/moq4/wiki/Quickstart
Update
I tryied to simulate your scenario and this code works fine (using .net test framework):
//Create an interface for your DBService
public interface IDBService
{
List<Transaction> GetDataFromDB(List<long> ids);
void InsertIntoTable(List<long> data);
}
//DLL class now receive a interface by dependency injection instead a concrete class
public class MyDLL
{
private readonly IDBService _dbService;
public MyDLL(IDBService dbService)
{
_dbService = dbService;
}
public string TestMethod(List<long> data)
{
var temp = _dbService.GetDataFromDB(data);//will be returned yourMockedData and assigned to temp
_dbService.InsertIntoTable(data);
//... rest of method implementation...
return string.Empty; //i've returned a empty string because i don't know your whole method implementation
}
}
//Whole method class implementation using .net test frameork
[TestClass]
public class UnitTest1
{
private Mock<IDBService> _dbService;
MyDLL _myDll;
[TestInitialize]
public void Setup()
{
_dbService = new Mock<IDBService>();
//setup your dbService
_dbService.Setup(x => x.InsertIntoTable(new List<long>()));
//data that will be returned when GetDataFromDB get called
var yourMockedData = new List<Transaction>
{
{
new Transaction{ Amount = 1, CompanyId = 123, ID = 123}
},
{
new Transaction{ Amount = 2, CompanyId = 321, ID = 124}
}
};
_dbService.Setup(x => x.GetDataFromDB(new List<long>())).Returns(yourMockedData);
//instantiate MyDll with mocked dbService
_myDll = new MyDLL(_dbService.Object);
}
[TestMethod]
public void TestMethod1()
{
//Testing MyDll.TestMethod()
var response = _myDll.TestMethod(new List<long>());
//Assert...Whatever do you want to test
}
[TestMethod]
public void TestMethod2()
{
//Testing dbService deirectly
var response = _dbService.Object.GetDataFromDB(new List<long>());
//Assert...Whatever do you want to test
}
}
Another alternative is to use an in-memory data context where schema for your real database is replicated in-memory of your unit test with an ORM like Entity Framework (which is part of the .NET Framework libraries) to abstract your queries and stored procedures into testable classes. This would clean up your data access C# code and make it more testable.
To use your test data context with Entity Framework, try a tool such as Entity Framework Effort ( https://entityframework-effort.net/ ) or if you are using .NET Core, the built-in in-memory provider using EF Core. I have an example in my site where I use in-memory providers in .NET Core ( http://andrewhalil.com/2020/02/21/using-in-memory-data-providers-to-unit-test-net-core-applications/ ).
Directly testing backend SQL objects (from test databases) like stored procedures or large datasets is the same as integration testing, which is different from what you are aiming to achieve here. In this case use mocking for your unit tests and integration testing to test the actual backend stored procedure.
Let me put my 2 cents in here.
From my experience, attemtps to mock/replace a data storage for tests is usually not a good idea, due to the limitations and behavior differences of the mocks/in-memory databases in comparison to the real database.
Moreover mocks usually lead to fragile tests due to the way assertions are made, as you basically depend on the implementation details of the system under tests instead of examining just its public surface. By asserting things like "this service called another service 2 times after this method is executed" you are making your tests aware of logic, which they shouldn't be aware of. It's an internal detail, which is possibly a subject of frequent changes, so any application update will lead to test failures, while the logic is still correct. And high amount of tests false positives isn't surely good.
I'd rather recommend that you use a real database for tests, which need access to data storage, while trying to keep a quantity of such tests minimal. Most of the logic should be covered by unit tests instead. It might require some application design changes, but those are usually for good.
And there are different tricks then to achieve the best performance of tests, which are using a real database:
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.