I have a service called UserService (this is obviously made up)
private readonly IAuthenticationService _authenticationService;
private readonly IUserRepository _userRepository;
public UserService(
IUserRepository userRepository,
IAuthenticationService authenticationService)
{
_userRepository = userRepository;
_authenticationService = authenticationService;
}
public async Task UpdateUser(UserDTO userDTO)
{
var authenticationDetails = await _authenticationService.Authenticate(userDTO.Id);
if (authenticationDetails.Success)
{
var user = _userRepository.GetUser(userDTO.Id);
user.Update(userDTO);
_userRepository.SaveChanges();
}
}
The IUserRepository implementation looks like this:
public UserRepository : Repository
{
private readonly UserContext _userContext;
public UserRepository(UserContext context) : base(context)
{
_userContext = context;
}
...
}
What I want to do, is have two service tests like these:
UpdateUser_WithSuccessfulAuthentication_UpdatesUser
UpdateUser_WithFailedAuthentication_DoesNothing
If following correct unit testing principles, I want to only consider the output (in this case, check if the user has been updated).
This way I can test the business logic inside the service method UpdateUser (which in this case is that single condition that checks if the user is authenticated. In reality there might be some more complex logic).
Currently I have considered one option for this, but haven't been able to make it work:
I can mock the IAuthenticationService using Moq, and have an in-memory-database context that would be used for the IUserRepository. So the first unit test would look something like this:
public void UpdateUser_WithSuccessfulAuthentication_UpdatesUser()
{
// Arrange
var authenticationServiceMock = new Mock<IAuthenticationService>();
var contextOptions = new DbContextOptionsBuilder<UserContext>()
.UseInMemoryDatabase("Db")
.Options;
using var context = new UserContext(contextOptions);
var userRepository = new UserRepository(context);
var userService = new UserService(userRepository, authenticationServiceMock.Object);
... setup the authentication service methods, in this case Authenticate
var userDTOToCreate = GetUserTestDoubleToCreate();
var userDTOToUpdate = GetUserTestDoubleToUpdate();
userRepository.Insert(userDTOToCreate);
// Act
userService.UpdateUser(userDTOToUpdate);
// Assert
... assert user was updated etc.
}
The problem with this is I cannot use the context inside this service since it has already been disposed.
I have no other ideas on how to do this.
Related
Let's say I have a web api application which has this service interface.
public interface IMyService
{
Task<int> Process(string processType);
}
and this is my service class. I am using option pattern.
public class MyService : IMyService
{
private readonly MyOptions _myOptions;
private readonly MyContext _myContext;
private readonly IHttpClientFactory _httpClientFactory;
public MyService(IOptions<myOptions> myOptions,
MyContext myContext,
IHttpClientFactory httpClientFactory)
{
_myOptions = myOptions.Value;
_myContext = myContext;
_httpClientFactory = httpClientFactory;
}
}
and this is how I register it in Program.cs. I am using .NET 6 framework.
var builder = WebApplication.CreateBuilder(args);
var myConnStr =
builder.Configuration.GetConnectionString("MyConnection");
// EF Core
builder.Services.AddDbContext<MyContext>(options =>
{
options.UseSqlServer(myConnStr);
});
builder.Services.AddOptions().Configure<MyOptions>(builder.Configuration.GetSection("ABC"));
builder.Services.AddHttpClient();
builder.Services.AddScoped<IMyService, MyService>();
// some code are removed for brevity
How do I test Process method in my service using Xunit? Note: Although the web api is calling this method, I do not want to test the web api. The reason is because this is actually a background process. Thus there won't be much thing return in the controller (web api). I also don't feel the need to mock it if possible to simplify the integration test.
I managed to do something like this.
public class MyServiceTest
{
private const string sqlServerConnection = "xxx";
private IMyService _myService;
private MyContext _myContext;
public MyServiceTest()
{
_configuration = new ConfigurationBuilder().AddJsonFile("appsettings.test.json").Build();
}
[Fact]
public async Task WhenProcessIsInValid_ThenReturn0()
{
// Arrange
SetupSqlServerContext();
await SetupMockData();
// _myService = new MyService(_myContext);
}
}
private void SetupSqlServerContext()
{
var options = new DbContextOptionsBuilder<MyContext>();
options.UseSqlServer(sqlServerConnection);
_myContext = new SqlServer.MyContext(options.Options);
}
I am stuck on creating the instance for MyService. How do I pass it IHttpClientFactory and IOptions<yOptions> to its constructor?
Use one of the mocking frameworks like Moq, for example, to mock the IHttpClientFactory.
It will look similar to this
var factoryMock = new Mock<IHttpClientFactory>();
factoryMock
.Setup(factory => factory.CreateClient(It.IsAny<string>()))
.Returns(httpClientMock.Object);
Check this answer to learn how to mock HttpClient.
For IOptions<MyOptions> it's even simpler, just create an instance of it using this code
var options = Options.Create(new MyOptions{ });
And finally, instantiate the MyService
_myService = new MyService(options, _myContext, factoryMock.Object);
I have this error message :
An error was generated for warning 'Microsoft.EntityFrameworkCore.Infrastructure.ManyServiceProvidersCreatedWarning': More than twenty 'IServiceProvider' instances have been created for internal use by Entity Framework. This is commonly caused by injection of a new singleton service instance into every DbContext instance. For example, calling 'UseLoggerFactory' passing in a new instance each time--see https://go.microsoft.com/fwlink/?linkid=869049 for more details. This may lead to performance issues, consider reviewing calls on 'DbContextOptionsBuilder' that may require new service providers to be built. This exception can be suppressed or logged by passing event ID 'CoreEventId.ManyServiceProvidersCreatedWarning' to the 'ConfigureWarnings' method in 'DbContext.OnConfiguring' or 'AddDbContext'.
When I run all Unit Test together
Setup
private readonly DbContextOptions<ApplicationDbContext> _contextOptions;
private readonly DbContextOptions<ApplicationDbContext> _inMemoryContextOptions;
public TestConstructor()
{
// Test for real database READ
_contextOptions = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseSqlServer(_connectionString)
.Options;
// Test InMemory CREATE UPDATE DELETE
_inMemoryContextOptions = DbContextOptionsBuilder();
SeedInMemoryTestDb(_inMemoryContextOptions);
}
private static DbContextOptions<ApplicationDbContext> DbContextOptionsBuilder()
{
return new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString(),new InMemoryDatabaseRoot())
.Options;
}
Unit Test
[FACT]
public void Test1()
await using var context = new ApplicationDbContext(_contextOptions);
//... Assert.Equal()
[FACT]
public void Test2()
await using var context = new ApplicationDbContext(_inMemoryContextOptions);
//... Assert.Equal()
I have both Setup and Unit Test in 5 or 6 class.
I think I need to re-use the same context for every test but I don't achieve to do that.
[CollectionDefinition("SharedDbContext")]
public class DatabaseCollection : ICollectionFixture<DatabaseFixture> { }
public class DatabaseFixture : IDisposable
{
public ApplicationDbContext ApplicationDbContext;
public ApplicationDbContext InMemoryApplicationDbContext;
public DatabaseFixture()
{
// Test for real database READ
var contextOptions = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseSqlServer(_connectionString)
.Options;
//// Test InMemory CREATE UPDATE DELETE
var inMemoryContextOptions = DbContextOptionsBuilder();
ApplicationDbContext = new ApplicationDbContext(contextOptions);
InMemoryApplicationDbContext = new ApplicationDbContext(inMemoryContextOptions);
SeedInMemoryTestDb(inMemoryContextOptions);
}
}
I have an issue regarding integration testing within a razor application combined with MVVM. All my classes use ApplicationDbContext.
The Test class:
[TestFixture]
public class ApiParserControllerTests
{
private readonly ApplicationDbContext _dbContext;
public ApiParserControllerTests(ApplicationDbContext dbContext)
{
_dbContext = dbContext;
}
[Test]
public void IsOptionValid_Teacher_ShouldReturnTrue()
{
var model = new ApiParserController(_dbContext);
var assign = model.IsOptionValid("Teacher");
Assert.AreEqual(true, assign.Value);
}
The method class:
public class ApiParserController : Controller
{
private readonly ApplicationDbContext _dbContext;
public ApiParserController(ApplicationDbContext dbContext)
{
_dbContext = dbContext;
}
[HttpPost]
public JsonResult IsOptionValid(string Option)
{
return Json(_dbContext.Import.Any(x => x.Option.ToLower() == Option.ToLower()));
}
}
Running this gives me the error in the title. I have tried adding an empty constructor to solve this problem, however this just makes the ApplicationDbContext null.
What am I missing here?
EDIT:
I have added a unit test for this method that mocks the database using the inMemory:
[Test]
public void IsOptionValid_Teacher_ShouldReturnTrue()
{
//Arrange
var optionsbuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
optionsbuilder.UseInMemoryDatabase(databaseName: "TeacherDB");
var _dbContext = new ApplicationDbContext(optionsbuilder.Options);
JsonResult json = new JsonResult(true);
_dbContext.ImportOption.Add(new ImportOption { Id = 1, isUnique = 1, Option = "Teacher" });
_dbContext.SaveChanges();
//Act
var model = new ApiParserController(_dbContext);
var assign = model.IsOptionValid("Teacher");
//Assert
Assert.AreEqual(true, assign.Value);
}
You defined a fixture with a constructor that takes an ApplicationDbContext. That means you need to supply NUnit with such an object so that it can call the constructor.
OTOH, by using TestFixture without any arguments, you are telling NUnit to use a default constructor. That's the source of the original error message, since the class had no default constructor.
Adding a default constructor makes it possible for NUnit to construct your fixture class, but that still doesn't give you the dbContext you need in order to run the test.
Normally, you would supply an argument to the fixture constructor by passing it as an argument to the TestFixtureAttribute. This may be difficult to do in your case and it may be simpler to either construct the context in OneTimeSetUp for the class or to mock the context. Which you do depends on exactly what you are trying to test, which isn't clear from the question.
In one of your comments, you mention wanting to use the "current dbContext". This might be the key, if you can explain it further. What do you mean by "current" here? Where is that context created? Then we can figure out how your test can get access to it.
Remove default constructor in your ApiParserControllerTests, use [SetUp] function instead.
[TestFixture]
public class ApiParserControllerTests
{
private ApplicationDbContext _dbContext;
[SetUp]
public void SetUp(){
// initialize here
_dbContext = new ApplicationDbContext();
}
[Test]
public void IsOptionValid_Teacher_ShouldReturnTrue()
{
var model = new ApiParserController(_dbContext);
var assign = model.IsOptionValid("Teacher");
Assert.AreEqual(true, assign.Value);
}
}
I want to make unit tests for my project using a fake context (i'm currently using moq for that).
I have the following classes:
EpisodiosService.cs
public class EpisodiosService : IService<Episodio>
{
private Context _context;
public EpisodiosService(Context context = null)
{
if (context == null)
{
context = new Context();
}
_context = context;
}
...
}
TesteHelper.cs
public class TesteHelper
{
public static List<Episodio> lstEpisodios { get; set; }
public static Mock<Context> mockContext { get; set; }
public static Mock<Context> GerarMassaDeDados()
{
...
var mockSetEpisodio = new Mock<DbSet<Episodio>>();
mockSetEpisodio.As<IQueryable<Episodio>>().Setup(m => m.Provider).Returns(lstEpisodios.AsQueryable().Provider);
mockSetEpisodio.As<IQueryable<Episodio>>().Setup(m => m.Expression).Returns(lstEpisodios.AsQueryable().Expression);
mockSetEpisodio.As<IQueryable<Episodio>>().Setup(m => m.ElementType).Returns(lstEpisodios.AsQueryable().ElementType);
mockSetEpisodio.As<IQueryable<Episodio>>().Setup(m => m.GetEnumerator()).Returns(lstEpisodios.AsQueryable().GetEnumerator());
mockContext = new Mock<Context>();
mockContext.Setup(x => x.Episodio).Returns(mockSetEpisodio.Object);
EpisodiosService episodiosService = new EpisodiosService(mockContext.Object);
return mockContext;
}
Episodio.cs
public class Episodio : ModelBase
{
...
public Episodio()
{
nIdEstadoEpisodio = Enums.EstadoEpisodio.Ignorado;
lstIntEpisodios = new List<int>();
lstIntEpisodiosAbsolutos = new List<int>();
}
public bool IdentificarEpisodio()
{
...
EpisodiosService episodiosService = new EpisodiosService();
List<Episodio> lstEpisodios = episodiosService.GetLista(oSerie);
...
}
So, if in the test method i put some code like var service = new EpisodiosService(TesteHelper.GerarMassaDeDados()) and work with this service i would get the mocked content as intended, but there are some methods inside the some entities that consumes the service and i cannot pass the mocked context like at the Episodio.IdentificarEpisodio(), and if i create an instance of Episodio and call IdentificarEpisodio(), it will not use the mocked context because it isn't passed.
Is there a way to make the service use the mocked context without changing its signature (to IdentificarEpisodio(Context context) for exemple)?
I didn't want to change it's signature because there are a lot of methods that have this same problem and that i would have to change, and i don't think it would be nice to change it all...
Thanks in advance.
To my opinion best way to solve that issue will be usage of dependency injection (you can use ninject or any other lib for this). Then you will be able to configure what context to use in any case.
If you using ninject easier solution will be create interface IContext and pass it as parameter in to service constructors like:
public class EpisodiosService : IService<Episodio>
{
private Context _context;
public EpisodiosService(Context context)
{
_context = context;
}
...
}
Next step is to configure injection core, where you can set what implementation to use for each interface in constructor parameters of class, that will be injected.
For development project:
var kernel = new StandardKernel();
kernel.Bind<IContext>().To<Context>();
For unit tests:
var kernel = new StandardKernel();
kernel.Bind<IContext>().ToMethod(e => TesteHelper.GerarMassaDeDados());
Then you can get your services using this core:
var service = kernel.Get<EpisodiosService>();
In this way you will have required context for each case.
Please note that there are much more options to configure injection, for example you could inject in public properties marked with InjectAttribute or create more complex and general binding rules.
As easier solution you can just create some method CreateContext() that will return required type of context depending on some settings and use it in all your methods. For example:
Context CreateContext()
{
if (isTest)
return TesteHelper.GerarMassaDeDados();
return new Context();
}
But this solution is less flexible than dependency injection.
I started doing some experimentation with unit testing so that we can include them in our domain layer. However i dont know if I'm following the right path, thus i'm going to explain what i'm currently doing to see if i'm on the right track. Basically the architecture is like the following there is Domain Layer containing domain models and domain services (ex. User class and UserService class). Then Domain layer communicates with the DAL which implements the Generic Repository pattern together with the Unit of Work. Each domain service class in it's constructor accepts an IUnitOfWork interface, like the following:
public class UserService: IUserService
{
private readonly IUnitOfWork _unitOfWork;
public UserService(IUnitOfWork unitOfwork)
{
this._unitOfWork = unitOfwork;
}
}
In order to creat the unit tests, i decided to go with FakeItEasy framework. So in a UserServiceTest class i did the following:-
private IUserService _userService;
private const int userID = 2013;
[TestInitialize]
public void Initialize()
{
_userService = A.Fake<IUserService>();
A.CallTo(() => _userService.GetUserById(userID)).Returns(new User
{
UserID = userID,
RegistrationDate = DateTime.Now,
});
}
[TestMethod]
public void GetUserByID()
{
var user = _userService.GetUserById(userID);
Assert.IsInstanceOfType(user, typeof(Domain.User));
Assert.AreEqual(userID, user.userID);
}
When I run the tests, they pass. Is it the correct way of implementing unit tests? Before I was trying a different approach however FakeItEasy was failing with a ProxyGenerator exception. What i was doing is this:-
[TestInitialize]
public void Initialize()
{
_unitOfWork = A.Fake<IUnitOfWork>();
A.CallTo(() => _unitOfWork.UserRepository.FindById(userID)).Returns(new UserDto
{
UserID = userID,
RegistrationDate = DateTime.Now,
});
AutoMapper.Mapper.CreateMap<UserDto, User();
}
[TestMethod]
public void GetUserByID()
{
var userService = new UserService(_unitOfWork);
var user = userService.GetUserById(userID);
Assert.IsInstanceOfType(user, typeof(Domain.User));
Assert.AreEqual(userID, user.userID);
}
And this was throwing the below exception:-
Result Message:
Initialization method Initialize threw exception. System.ArgumentNullException: System.ArgumentNullException: Value cannot be null.
Parameter name: callTarget.
Result StackTrace:
at FakeItEasy.Creation.ProxyGeneratorSelector.MethodCanBeInterceptedOnInstance(MethodInfo method, Object callTarget, String& failReason)
at FakeItEasy.Configuration.DefaultInterceptionAsserter.AssertThatMethodCanBeInterceptedOnInstance(MethodInfo method, Object callTarget)
at FakeItEasy.Configuration.FakeConfigurationManager.AssertThatMemberCanBeIntercepted(LambdaExpression callSpecification)
at FakeItEasy.Configuration.FakeConfigurationManager.CallTo[T](Expression`1 callSpecification)
at FakeItEasy.A.CallTo[T](Expression`1 callSpecification)
Any feedback would be greatly appreciated. Thanks!
I think your original (second, in the question) test was failing because _unitOfWork.UserRepository is coming back as null in Initialize. Normally FakeItEasy will create an fake object when chained properties are used, but I'm guessing (I have to guess because I don't know anything about the type of UserRepository) that UserRepository's type is not fakeable. In that case, you'd get a null back from _unitOfWork.UserRepository.
Let me jump back to your second test (which was first in your question), then we'll return to what I think you might want to do here.
Looking at your test,
var user = _userService.GetUserById(userID);
Assert.IsInstanceOfType(user, typeof(Domain.User));
Assert.AreEqual(userID, user.userID);
I see a flaw. You're invoking a method on _userService directly, but _userService is a fake object, so the test doesn't actually involve any of the production code. It's really only exercising FakeItEasy.
I think what we want is sort of a blended approach - something that will exercise the code in a real UserService, without worrying about UserRepository. Maybe something similar to (and I'm not using a compiler here, and don't know what methods are on IUnitOfWork so take this with a grain of salt)
[TestInitialize]
public void Initialize()
{
_unitOfWork = A.Fake<IUnitOfWork>();
A.CallTo(() => _unitOfWork.GetUserById(userID))
.Returns(new User
{
UserID = userID,
RegistrationDate = DateTime.Now,
});
}
[TestMethod]
public void GetUserByID()
{
var userService = new UserService(_unitOfWork);
var user = userService.GetUserById(userID);
Assert.IsInstanceOfType(user, typeof(Domain.User));
Assert.AreEqual(userID, user.userID);
}
Or, if there's nothing useful on IUnitOfWork except for UserRepository, then I think the next step would be to investigate why the type of UserRepository wasn't fakeable (if my guess was right) - is it sealed? Does it lack appropriate and accessible constructors?