.NET Core how to unit test service? - c#

I have build a WebAPI and want to create a unit test project to have my services tested automatically.
The flow of my WebAPI is simple:
Controller (DI Service) -> Service (DI Repository) -> _repo CRUD
Suppose I have a service like:
public int Cancel(string id) //change status filed to 'n'
{
var item = _repo.Find(id);
item.status = "n";
_repo.Update(item);
return _repo.SaveChanges();
}
And I want to build a unit test, which just use InMemoryDatabase.
public void Cancel_StatusShouldBeN() //Testing Cancel() method of a service
{
_service.Insert(item);
int rs = _service.Cancel(item.Id);
Assert.Equal(1, rs);
item = _service.GetByid(item.Id);
Assert.Equal("n", item.status);
}
I've searched other related question, found that
You can't use dependency injections on test classes.
I just want to know if there is any other solution to achive my unit test idea?

When unit testing, you should just supply all the dependencies of the class you are testing explicitly. That is dependency injection; not having the service construct its dependencies on its own but making it rely on the outer component to provide them. When you are outside of a dependency injection container and inside a unit test where you are manually creating the class you are testing, it’s your responsibility to provide the dependencies.
In practice, this means that you either provide mocks or actual objects to the constructor. For example, you might want to provide a real logger but without a target, a real database context with a connected in-memory database, or some mocked service.
Let’s assume for this example, that the service you are testing looks like this:
public class ExampleService
{
public ExampleService(ILogger<ExampleService> logger,
MyDbContext databaseContext,
UtilityService utilityService)
{
// …
}
// …
}
So in order to test ExampleService, we need to provide those three objects. In this case, we will do the following for each:
ILogger<ExampleService> – we will use a real logger, without any attached target. So any call on the logger will work properly without us having to provide some mock, but we do not need to test the log output, so we do not need a real target
MyDbContext – Here, we’ll use the real database context with an attached in-memory database
UtilityService – For this, we will create a mock which just setups the utility method we need inside the methods we want to test.
So a unit test could look like this:
[Fact]
public async Task TestExampleMethod()
{
var logger = new LoggerFactory().CreateLogger<ExampleService>();
var dbOptionsBuilder = new DbContextOptionsBuilder().UseInMemoryDatabase();
// using Moq as the mocking library
var utilityServiceMock = new Mock<UtilityService>();
utilityServiceMock.Setup(u => u.GetRandomNumber()).Returns(4);
// arrange
using (var db = new MyDbContext(dbOptionsBuilder.Options))
{
// fix up some data
db.Set<Customer>().Add(new Customer()
{
Id = 2,
Name = "Foo bar"
});
await db.SaveChangesAsync();
}
using (var db = new MyDbContext(dbOptionsBuilder.Options))
{
// create the service
var service = new ExampleService(logger, db, utilityServiceMock.Object);
// act
var result = service.DoSomethingWithCustomer(2);
// assert
Assert.NotNull(result);
Assert.Equal(2, result.CustomerId);
Assert.Equal("Foo bar", result.CustomerName);
Assert.Equal(4, result.SomeRandomNumber);
}
}
In your specific Cancel case, you want to avoid using any methods of the service you are not currently testing. So if you want to test Cancel, the only method you should call from your service is Cancel. A test could look like this (just guessing the dependencies here):
[Fact]
public async Task Cancel_StatusShouldBeN()
{
var logger = new LoggerFactory().CreateLogger<ExampleService>();
var dbOptionsBuilder = new DbContextOptionsBuilder().UseInMemoryDatabase();
// arrange
using (var db = new MyDbContext(dbOptionsBuilder.Options))
{
// fix up some data
db.Set<SomeItem>().Add(new SomeItem()
{
Id = 5,
Status = "Not N"
});
await db.SaveChangesAsync();
}
using (var db = new MyDbContext(dbOptionsBuilder.Options))
{
// create the service
var service = new YourService(logger, db);
// act
var result = service.Cancel(5);
// assert
Assert.Equal(1, result);
}
using (var db = new MyDbContext(dbOptionsBuilder.Options))
{
var item = db.Set<SomeItem>().Find(5);
Assert.Equal(5, item.Id);
Assert.Equal("n", item.Status);
}
}
Btw. note that I’m opening up a new database context all the time in order to avoid getting results from the cached entities. By opening a new context, I can verify that the changes actually made it into the database completely.

Related

What do I need to unit-test for this line of code using AutoMapper?

I've never used AutoMapper before, so I'm wondering what unit-tests I need to write for this specific line of code:
IEnumerable<UserDetail> userDetails =
this.mapper.Map<IEnumerable<Entities.User>, IEnumerable<UserDetail>>(users);
the line in question is in this method:
public IEnumerable<UserDetail> GetUserDetails()
{
var users = this.userData.GetUsers();
//question is about testing this line
IEnumerable<UserDetail> userDetails =
this.mapper.Map<IEnumerable<Entities.User>, IEnumerable<UserDetail>>(users);
return userDetails;
}
Currently the unit-test provides a mock list of Users to return from this.userData.GetUsers() and then verifies the output userDetails contains the same data in this.userData.GetUsers()
This first unit-tests looks like this:
[Test]
public async Task VerifyAutoMapperProfile()
{
//Assert
this.MapperConfig.AssertConfigurationIsValid();
}
[Test]
public async Task UserService_GetUsers_Returns_IEnumerableUserDetails()
{
//Arrange
IList<Entities.User> usersFromData = this.GetMockUsers();
Mock<IUserDataAccess> mockUserData = new Mock<IUserDataAccess>();
mockUserData
.Setup(d => d.GetUsers())
.Returns(usersFromData);
//this loads the same AutoMapper.Profile that the application uses
IMapper mapper = this.MapperConfig.CreateMapper();
var userService = new UserService(mockUserData.Object, mapper);
//Act
var userDetails = userService.GetUserDetails();
//Assert
userDetails.Should().BeAssignableTo<IEnumerable<UserDetail>>();
userDetails.Should().HaveCount(usersFromData.Count);
userDetails.Should().NotContainNulls();
userDetails.Should().OnlyHaveUniqueItems();
userDetails.Should().AllBeOfType<UserDetail>();
foreach (Entities.User user in usersFromData)
{
var userDetail = new UserDetail {FullName = user.FullName, Guid = user.Guid};
userDetails.Should().ContainEquivalentOf(userDetail);
}
}
So question is, should I add any coverage in this test for the line I've indicated, or should I be creating separate tests for that line? (I will be creating more unit-tests in general for this method - but my question is specifically asking about the IMapper.Map method. Do I need to cover failures/exceptions thrown by that method? Is it enough just to have this.MapperConfig.AssertConfigurationIsValid()?
In your GetUserDetails() method, you do 2 things. First is loading users, second is converting. So basically, your unit test just need to verify those 2 things. First is GetUsers() invoked or not? Second is Map() invoked or not? And 1 extra thing, if you load 10 users, you expect to get back 10 users (do NOT check the user properties).
WHY you don't need to check user properties? Because the mapping logic is not in your method, so you don't need to unit-test that.

Do I need multiple assert? xUnit Test

this is my first time writing unit test, and I just have a few questions. Im using in memory database to test my services and I'am wondering if I'm doing it correctly. My first question is do I need multiple assert on all of my service call? like do i need assert for InsertProduct? Second, Am I over testing this for using new instance of context on every service call?
[Fact]
public void ProductService_DeleteProduct_Test()
{
// arrange
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase(databaseName: "ProductService_DeleteProduct_Test")
.Options;
var product = new Product() { Id = Guid.NewGuid(), Name = "Product"};
// act
// insert
using (var context = new ApplicationDbContext(options))
{
var service = new Service(context);
service.ProductService.InsertProduct(product);
}
// delete
using (var context = new ApplicationDbContext(options))
{
var service = new Service(context);
service.ProductService.DeleteProducts(new List<Guid> { product.Id });
}
// assert
using (var context = new ApplicationDbContext(options))
{
var service = new Service(context);
Assert.Equal(0, service.ProductService.GetAllProducts().Count);
}
}
I would make an objection on the structure of your test. Namely, you are using the service (production code) to prepare the underlying database. And you are also using the production code to make the assertion.
If any part of the production code is incorrect, this test will fail. However, this test is designed to assert that the delete feature is doing right.
Therefore, I would rewrite entire test in the following way:
[Fact]
public void ProductService_DeleteProduct_Test()
{
// arrange
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase(databaseName: "ProductService_DeleteProduct_Test")
.Options;
var product = new Product() { Id = Guid.NewGuid(), Name = "Product"};
// Insert object using other means, i.e. direct INSERT statement
// act
using (var context = new ApplicationDbContext(options))
{
var service = new Service(context);
service.ProductService.DeleteProducts(new List<Guid> { product.Id });
}
// assert
// Execute SELECT COUNT(*) instruction to fetch previously existing row
Assert.Equal(0, rowsCount);
}
In this way, you will only touch production code in the acting part of the test. That is the part in which you are using the service object to delete an object from the database.
Subsequent assertion is done against a scalar value count which is fetched as the result of a raw SELECT statement executed directly on the storage.
Bottom line is that none of the parts of your test are now depending on correctness of the production code, except the DeleteProducts method which is in fact the method under test.
And, consequently, the answer to your question is that there is only one assertion to write in this test.
Answering your first question, I'd say no. Since this is a unit test and you're testing the delete specifically. I consider the insert part of the setup since you're getting the system into a state where you want to test the delete. To Zoran Horvat's point, if you can, put a row in the database via some means other than the service itself.
To answer your second question, it seems unnecessary to have three using blocks where you're new'ing up the service three times. I'd put the insert, delete, and assert in the same using, making use of one instance of the SUT, or service.
However, if you have multiple tests that all require there to be a row in the database, consider moving the insert into a SetUp method with the [SetUp] attribute that every test can call before hand. In that case, you'd be using multiple instances of the context.

Unit Testing Codeactivity Workflow

I am trying to write a unit test for a CodeActivity Workflow. My test so far is only
[TestMethod]
public void BusinessUnitTest()
{
WorkflowInvoker invoker = new WorkflowInvoker(new DespatchStockOrder());
invoker.Extensions.Add<ITracingService>(() => tracingService);
invoker.Extensions.Add<IWorkflowContext>(() => workflowContext);
invoker.Extensions.Add<IOrganizationServiceFactory>(() => factory);
IDictionary<string, object> outputs = invoker.Invoke();
}
This works fine and runs the workflow. However I get a null referrence when I hit this line in the workflow.
// Create the context
IWorkflowContext context = executionContext.GetExtension<IWorkflowContext>();
if (context == null)
{
throw new InvalidPluginExecutionException("Failed to retrieve workflow context.");
}
Entity entity = new Entity(context.PrimaryEntityName) //This line errors with PrimaryEntityName null
{
Id = context.PrimaryEntityId,
LogicalName = context.PrimaryEntityName
};
My question is how do I get context.PrimaryEntityName witha value in a unit test
If you are building the context manually then you will need to populate the PrimaryEntityName in the [TestMethod]. You should probably look further into how your workflowContext test object is constructed.
[TestMethod]
public void BusinessUnitTest()
{
workflowContext.PrimaryEntityName = "Entity name";
WorkflowInvoker invoker = new WorkflowInvoker(new DespatchStockOrder());
invoker.Extensions.Add<ITracingService>(() => tracingService);
invoker.Extensions.Add<IWorkflowContext>(() => workflowContext);
invoker.Extensions.Add<IOrganizationServiceFactory>(() => factory);
IDictionary<string, object> outputs = invoker.Invoke();
}
You might want to try FakeXrmEasy. It's an open source unit testing library I'm maintaining to facilitate unit testing for Dynamics Crm without mocking anything.
You get a reference to a workflow context and inject whatever parameters you need, including Primary Entity names and Id's.
But the most common crm messages are already mocked (retrieve multiple requests, CRUD, fetchxml, LINQ, QueryExpressions, and so no...)

Unit Test Using Moq

I am unit-testing an async method that returns a List<T>. This method has a dependency on a mapping class/interface. In my unit-test, I am mocking the mapping class using moq. The test runs okay, and the returned list has items, but the values of the items is null. I think the problem is because I haven't stubbed-out the mapping classes methods properly. I don't have a lot of experience with testing, so any guidance is appreciated.
Test Method:
[TestMethod]
[TestCategory("CSR.Data.Tests.Services.ServiceSearchTest")]
public void SearchAccount()
{
// Arrange
var mapper = new Mock<CSR.Data.Mapping.Interfaces.IMapper<Account, AccountDTO>>();
mapper.Setup(i => i.Initialize());
mapper.Setup(i => i.ToDomain(It.IsAny<AccountSearchResult>())).Returns(It.IsAny<Account>);
mapper.Setup(i => i.DomainToDto(It.IsAny<Account>())).Returns(It.IsAny<AccountDTO>);
var service = new ServiceSearch(null,mapper.Object);
string accountNumber = "123";
string accountName = "";
// Act
var results = service.SearchAccount(accountNumber, accountName);
// Assert
Assert.IsTrue(results.Result.Count >= 1);
}
Method/Class That I'm Testing:
public class ServiceSearch : IServiceSearch
{
public ServiceSearch(IMapper<Claim, ClaimDTO> claimMapper, IMapper<Account, AccountDTO> accountMapper)
{
_claimMapper = claimMapper;
_accountMapper = accountMapper;
}
public async Task<List<AccountDTO>> SearchAccount(string accountNumber, string accountName)
{
var accounts = new List<Account>();
var accountDTOs = new List<AccountDTO>();
var results = await Task.Run(() => base.AccountSearch(accountNumber, accountName).Result);
if (results != null && results.Count > 0)
{
//Map DH to Domain
_accountMapper.Initialize();
foreach (AccountSearchResult result in results)
{
accounts.Add(_accountMapper.ToDomain(result));
}
//Map Domain to DTO
foreach (Account account in accounts)
{
accountDTOs.Add(_accountMapper.DomainToDto(account));
}
}
return accountDTOs;
}
}
This isn't the best place to use a Mock object because you are going to spend a lot of time writing your test objects and mock results. The issue with the setup call is that you haven't configured anything to send back in the result. A correct example would be:
// you would fully configure this object
AccountDTO expectedResult = new AccountDTO();
mapper.Setup(i => i.ToDomain(It.IsAny<AccountSearchResult>())).Returns(expectedResult);
Now you can use the setup to configure different accountDTOs for different inputs.
You call also configure a callback to generate the account at test time:
mapper.Setup(i => i.ToDomain(It.IsAny<AccountSearchResult>())).Returns<AccountSearchResult>(sr => {
// build and return your dto here
});
However, unless your mapper is expensive to run or create, I think you'd better off just ensure that it is fully tested and acceptable and then use it to go ahead and generate the DTOs directly instead of trying to mock it out.
You don't actually setup an object in the ".Returns" call. You need to make sure to setup the ".Returns" to actually have an object with values.

How to unit test code that includes a database transaction

How to put unit around the below codes:
public DbContextTransaction QTTransactionBegin()
{
return Database.BeginTransaction();
}
public int CreateCampaign(CreateCampaignModel createCampaignModel)
{
using (var transaction = _qtContext.QTTransactionBegin())
{
try
{
var campaign = new Campaign();
campaign.CampaignCode = createCampaignModel.CampaignCode;
campaign.CampaignDescription = createCampaignModel.CampaignDescription;
campaign.CampaignDate = createCampaignModel.CampaignDate;
campaign.CampaignNotes = createCampaignModel.CampaignNotes;
campaign.OwnerUserID = createCampaignModel.OwnerUserID;
campaign.AddedOn = DateTime.Now;
campaign.AddedBy = createCampaignModel.OwnerUserName;
campaign.UpdatedOn = DateTime.Now;
campaign.UpdatedBy = createCampaignModel.OwnerUserName;
campaign.CampaignSegments = GetCampaignSegmentList(createCampaignModel);
var campaignId = AddOrUpdateCampaign(campaign);
transaction.Commit();
return campaignId;
}
catch (Exception ex)
{
transaction.Rollback();
}
}
return 0;
}
Could anyone advise me how to put unit test around above code ?
I tried the code as below :
Database_database;
[TestInitialize]
public void SetUp()
{
_qtDALMock = _factory.CreateMock<IQTDAL>();
_campaignRepository = new CampaignRepository(_qtDALMock.MockObject);
}
[TestMethod]
public void Check_CreateCampaign_Test()
{
// arrange
const int expectedCampaignId = 1;
var createCampaign = QueryToolDummies.CreateCampaignModel;
_database.BeginTransaction();
_qtDALMock.Expects.One.MethodWith(x => x.QTTransactionBegin())
.WillReturn(_database.BeginTransaction());
_qtDALMock.Expects.One.Method(x => x.AddOrUpdateCampaign(null))
.With(Is.TypeOf<Campaign>())
.WillReturn(expectedCampaignId);
// act
var result = _campaignRepository.CreateCampaign(createCampaign);
// assert
Assert.IsNotNull(result);
}
this _database.BeginTransaction() has a problem. the error says can't use like it.
Please advise.
One question? Why are you trying to start a transaction into a unit test?
Will be easy if you Mock your repository using Moq framework and return what you need to return from the repo.
In fact, I thought that start a BeginTransaction() in a unit test is not a good practice.
I hope this helps
I've experienced the same issue, it's quite tricky to work around.
I tried creating a wrapper class for the context that exposes a BeginTransaction() method, but ultimately you end up needing to mock the DbContextTransaction returned by BeginTransaction() when it comes to testing, but DbContextTransaction has neither an interface or a public constructor.
In the end I wrote a transaction manager class that creates and manages its own transaction and exposes methods for beginning, committing and rolling back the transaction. That manager class, and the service that returns it, can then be faked allowing the code using transactions to be fully tested.
I've written that up fully in this answer.
You are trying to unit test more than one unit.
Assuming that the code above is your 'data layer' / repository then you are performing an integration test because more than a single unit is involved in the test.
You could include setup / teardown for the database within your test class and call the SUT (subject under test) with valid and invalid data to validate expected behaviours.

Categories