I have a already written (was written years ago) C# function, I have been asked to cover this method with Unit Tests.
public string PlaceOrder(int requestId, string orderedby)
{
try
{
using (DatabaseContext dbContext = new DatabaseContext("myConnectionStringHere"))
{
var req = dbContext.Orders.Where(row => row.id == requestId).FirstOrDefault();
if (req == null)
return "not found";
req.status="A";
dbContext.SaveChanges();
return "found";
}
}
catch (Exception ex)
{
return "error";
}
}
Now while Unit testing I need to make sure that it does not write anything to database, so I have to MOQ
it. How can I MOQ
, it contains Using
block.
I know architecture could have been better and design patterns should have been followed but I am not allowed to change the structure of the application as it is a legacy application.
The general guidance here is to prefer Integration Test with in memory (w/o sqlite) database over unit testing.
Let me suggest you four helper libraries which can make your testing easier:
The prerequisite here is to mark your DbSet
as virtual
like this:
public virtual DbSet<Order> Orders { get; set; }
Then you can create a mock where you can populate your Orders
collection with some dummy data:
var initialOrders = new[]
{
new Order { ... },
new Order { ... },
};
var dbContextMock = new DbContextMock<DatabaseContext>(new DbContextOptionsBuilder<DatabaseContext>().Options);
var ordersDbSetMock = dbContextMock.CreateDbSetMock(db => db.Orders, initialOrders);
You have to rewrite your containing class of the PlaceOrder
method in a way to receive a DatabaseContext
parameter in the constructor to be able inject dbContextMock.Object
during testing.
In the assertion phase you can query your data and make assertion against it. Since you do not call Add
, Remove
or any other CRUD method, you can only Verify
the SaveChanges
call.
public void GivenAnExistingOrder_WhenICallPlaceOrder_ThenSaveChangesIsCalledOnce()
{
...
//Assert
dbMock.Verify(db => db.SaveChanges(), Times.Once);
}
public void GivenANonExistingOrder_WhenICallPlaceOrder_ThenSaveChangesIsCalledNever()
{
...
//Assert
dbMock.Verify(db => db.SaveChanges(), Times.Never);
}
EntityFrameworkCore.Testing
It is working more or less in the way as the previous library.
var dbContextMock = Create.MockedDbContextFor<DatabaseContext>();
dbContextMock.Set<Order>().AddRange(initialOrders);
dbContextMock.SaveChanges();
The assertions work in the same way.
A 3rd (less mature) library is called Moq.EntityFrameworkCore .
If you really keen to perform unit testing by avoiding in memory database then you should give a try to the MockQueryable
library.
const int requestId = 1;
var orders = new List<Order>();
var ordersMock = orders.AsQueryable().BuildMockDbSet();
ordersMock.Setup(table => table.Where(row => row.Id == requestId)).Returns(...)
Here you are basically mocking what should be the result of your Where
filter. In order to be able to use this the containing class of the PlaceOrder
should receive a DbSet<Order>
parameter via its constructor.
Or if you have an IDatabaseContext
interface then you can use that one as well like this:
Mock<IQueryable<Order>> ordersMock = orders.AsQueryable().Build();
Mock<IDatabaseContext> dbContextMock = ...
dbContextMock.Setup(m => m.ReadSet<Order>()).Returns(ordersMock.Object));
Many things should be changed here:
1:
Do not implement your connection string this way, directly in the code base. Instead, DI your database into your classes.
so this pseudo code should help out with the general idea.
public void ConfigureService(IServiceCollection serviceCollection)
{
...
string connectionString = //secure storage;
serviceCollection.AddDbContext<DatabaseContext>(options => {
options.UseSqlServer(connectionString);
});
...
}
And then
public class OrderRepository
{
private IServiceScopeFactory _serviceScopeFactory ;
public OrderRepository(IServiceScopeFactory serviceScopeFactory ){
_serviceScopeFactory = serviceScopeFactory ;
}
...
public string PlaceOrder(int requestId, string orderedby)
{
try
{
using (var context = serviceScopeFactory.CreateScope())
{
var req = context.Orders.Where(row => row.id == requestId).FirstOrDefault();
if (req == null)
return "not found";
req.status="A";
context.SaveChanges();
return "found";
}
}
catch (Exception ex)
{
return "error";
}
}
...
}
if you want to make an integration test, you can then use an InMemory db to emulate whatever you want. Or you can connect to a "real" db, and do it that way.
If you want to make it a unit test, you can see at this link: How to setup a DbContext Mock
2:
returning a string saying found/not found for a order being placed, seems extremely counter productive.
if your aim is to log this information, provider a DI logger, that can log this. (Try importing the ILogger interface, it's a microsoft extension on logging, can't remember the nuget package name) should enable you to log with DI very efficiently.
If your aim is to let a possible UI display this message, there is no way the message content should originate from back-end or domain logic.
At least not like this.
Then you should make an interface for a response, and return an implementation of said interface, that exists somewhere else as a minimum but even that is a bit like peeing your pants. (And contains a UI friendly message, can contain a possible stacktrace/exception), and other possible relevant information, like what Id you were trying to place an order on etc.)
You should make it something that happens at the interface between your UI and domain logic, provided that is what the string is intended for. Where you would expect to see error handling.
3: WTF is up with the catch? you just return error? Well? What error? you lose the stack-trace this way? Someone should be punished for that.
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.