I have implemented custom retry strategy on an API which is in .NET Core 2.2. Retry strategy should work only for transient errors from the database(Azure SQL). How can I generate transient errors, to test this feature?
I don't know your code, but this is one solution. The idea is to separate the retry logic from the actual action that needs to be retried, so that each can be tested.
A static RetryHandler could also work, but it depends on your need.
Psedo code, does not compile.
public RetryHandler : IRetryHandler
{
private static List<Type> transientErros = new List<Type>
{
typeof(TimeoutException),
typeof(SomeotherExceptionThatRequiresRetry),
}
public void RetryOnTransientError(Action action, int attempts = 3)
{
for (var i = 0; i < attempts; i++)
{
try
{
action();
return;
}
catch(Exception e)
{
if(transientErros.Contains(e.GetType())
continue;
throw;
}
}
}
}
public class UserRepository
{
private IMyDbContext context;
private IRetryHandler retryHandler;
public Repository(IMyDbContext context, IRetryHandler retryHandler)
{
this.context = context;
this.retryHandler = retryHandler;
}
public void InsertUser(User user)
{
retryHandler.RetryOnTransientError(() => DoInsertUser(user));
}
private void DoInsertUser(User user)
{
// insert logic
context.SaveChanges();
}
}
[Test]
public void InsertUserRetriesOnTransientError()
{
// Arrange
var contextMock = new Mock<IMyDbContext>();
var repository = new Repository(contextMock);
var user = CreateUser();
contextMock.Setup(x => x.SaveChanges()).Throws<TransientException>());
// Act
Assert.Throws<TransientException>(() => repository.InsertUser(user));
// Assert
// verify SaveChanges() was called 3 times
}
Related
I'm attempting to write a unit test for my repository class for its Create method that uses DbContext and the Add method.
My idea was to get a count of the number of existing records. Call the Create method. Get the new count of records and check that its incremented by 1.
However, when running the unit test, it errors when calling the Add method with the following error:
{"Cannot access a disposed context instance. A common cause of this error is disposing a context instance that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling 'Dispose' on the context instance, or wrapping it in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.\r\nObject name: 'DbContext'."}
I'm trying to understand why is this and how to overcome this?
public class MyDatabaseContext : DbContext
{
public MyDatabaseContext(DbContextOptions<MyDatabaseContext> options) : base(options)
{
}
public DbSet<Record> Records { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Record>();
}
}
public class Repository : IRepository
{
private readonly MyDatabaseContext _dbContext;
public Repository(MyDatabaseContext dbContext)
{
_dbContext = dbContext;
}
public Record Create(Record record)
{
try
{
using (_dbContext)
{
var response = _dbContext.Records.Add(record); //erroring line
_dbContext.SaveChanges();
return response.Entity;
}
}
catch (Exception ex)
{
return null;
}
}
public IEnumerable<Record> GetAll()
{
try
{
using (_dbContext)
{
return _dbContext.Records.ToList();
}
}
catch (Exception ex)
{
return null;
}
}
}
public interface IRepository
{
Record Create(Record record);
IEnumerable<Record> GetAll();
}
Startup.cs:
services.AddDbContext<MyDatabaseContext>(opt => opt.UseInMemoryDatabase("memoryDb"));
services.AddScoped<IRepository, Repository>();
Unit test:
[TestMethod]
public async Task Create_Successfully()
{
var repository = new Repository(await GetDbContext());
var existingRecords = repository.GetAll();
repository.Create(new Record());
var newRecords = repository.GetAll();
Assert.AreEqual(3, existingRecords.Count());
Assert.AreEqual(4, newRecords.Count());
}
private async Task<DbContext> GetDbContext()
{
var options = new DbContextOptionsBuilder<DbContext>().UseInMemoryDatabase(Guid.NewGuid().ToString()).Options;
var context = new DbContext(options);
context.Database.EnsureCreated();
if (await context.Records.CountAsync() <= 0)
{
for (int i = 1; i <= 3; i++)
{
context.Records.Add(new Records());
await context.SaveChangesAsync();
}
}
return context;
}
You need to remove the using statements in the GetAll and Create methods:
public Record Create(Record record)
{
try
{
using (_dbContext)
{
var response = _dbContext.Records.Add(record); //erroring line
_dbContext.SaveChanges();
return response.Entity;
}
}
catch (Exception ex)
{
return null;
}
}
To:
public Record Create(Record record)
{
try
{
var response = _dbContext.Records.Add(record); //erroring line
_dbContext.SaveChanges();
return response.Entity;
}
catch (Exception ex)
{
return null;
}
}
You don't need to worry about disposing the service as the conatiner will do that for you in production. In your tests you can do this if you want to clean things up:
[TestMethod]
public async Task Create_Successfully()
{
using (var context = await GetDbContext())
{
var repository = new Repository(context);
var existingRecords = repository.GetAll();
repository.Create(new Record());
var newRecords = repository.GetAll();
Assert.AreEqual(3, existingRecords.Count());
Assert.AreEqual(4, newRecords.Count());
}
}
I'm trying to test this class:
public class Tasks : ITaskEnumerableProvider
{
protected string ConnectionString;
DAL_EFCore.AdventureWorks2017Context CurrentContext;
public Tasks(DAL_EFCore.AdventureWorks2017Context currentContext)
{
CurrentContext = currentContext;
}
public Tasks(string connectionString)
{
ConnectionString = connectionString;
}
public DAL_EFCore.AdventureWorks2017Context GetContext()
{
if (CurrentContext != null)
return CurrentContext;
else
{
var serviceCollection = new ServiceCollection()
.AddDbContextPool<DAL_EFCore.AdventureWorks2017Context>
(
options => options.UseSqlServer(ConnectionString)
);
var serviceProvider = serviceCollection.BuildServiceProvider();
CurrentContext = serviceProvider.GetService<DAL_EFCore.AdventureWorks2017Context>();
return CurrentContext;
}
}
public IEnumerable<SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders> SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders()
{
return GetContext().Employee
.GroupBy(e => e.Gender)
.Select(n => new SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders
{
Gender = n.Key,
Count = n.Count()
})
.ToList();
}
}
And this one:
public class DataReports : IDataReports
{
protected ITaskEnumerableProvider TaskProvider;
protected string ConnectionString;
protected string DalModeSelected;
public DataReports() {}
public DataReports(DBConfig config)
{
ConnectionString = config.ConnectionString;
DalModeSelected = config.DAL;
}
public ITaskEnumerableProvider GetTaskProvider()
{
switch (DalModeSelected)
{
case "ADO":
return new ADOTaskProvider.Tasks(ConnectionString);
case "EFCore":
return new EFCoreTaskProvider.Tasks(ConnectionString);
default:
throw new FormatException("The format of the variable which represend the selected DAL was not correct");
}
}
public IEnumerable<SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders> SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders()
{
return GetTaskProvider().SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders().ToList();
}
I'm testing with this code:
[TestMethod]
public void SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrdersTest()
{
// Setup Mock Data and context
var options = new DbContextOptionsBuilder<DAL_EFCore.AdventureWorks2017Context>()
.UseInMemoryDatabase(databaseName: "SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrdersTest")
.Options;
using (var context = new DAL_EFCore.AdventureWorks2017Context(options))
{
InsertData(options);
}
using (var context = new DAL_EFCore.AdventureWorks2017Context(options))
{
// Mock EFCoreTaskProvider.Tasks
var mockEFCoreTaskProvider = new Mock<EFCoreTaskProvider.Tasks>(ConnectionString);
mockEFCoreTaskProvider.As<IGetContext>();
mockEFCoreTaskProvider.CallBase = true;
mockEFCoreTaskProvider.Setup(x => x.GetContext()).Returns(context);
// Mock CoreReportService.DataReports
var config = new DBConfig { DAL = "EFCore", ConnectionString = ConnectionString };
var mockDataReports = new Mock<DataReports>(config).As<IDataReports>();
mockDataReports.CallBase = true;
mockDataReports.Setup(x => x.GetTaskProvider()).Returns(mockEFCoreTaskProvider.Object);
var test = mockDataReports.Object.SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders().ToList();
Assert.IsTrue(test.Count == 1);
}
}
I'm using an inmemorydatabase to test the data but the test.Count of the TestCase has the count that corresponds from the real database.
If I make GetContext() and GetTaskProvider() virtual I'm getting the correct Count from the virtual database but I don't want them to be virtual, I also prefer if they were not public, what am I doing wrong?
This is a matter of design.
what am i doing wrong?
DataReports is tightly coupled to implementation concerns and is also violating Single Responsibility Principle (SRP) and Separation of Concerns (SoC).
By having DataReports create the providers, it is tightly coupled to them, and prevents you from being able to replace them when testing.
Abstract the provider creation out into its own concern
For example
//Abstraction
public interface ITaskProviderFactory {
ITaskEnumerableProvider GetTaskProvider();
}
//Implementation
public class DefaultTaskProviderFactory : ITaskProviderFactory{
private readonly DBConfig config;
public DefaultTaskProviderFactory(DBConfig config) {
this.config = config;
}
public ITaskEnumerableProvider GetTaskProvider() {
switch (config.DAL) {
case "ADO":
return new ADOTaskProvider.Tasks(config.ConnectionString);
case "EFCore":
return new EFCoreTaskProvider.Tasks(config.ConnectionString);
default:
throw new FormatException("The format of the variable which represent the selected DAL was not correct");
}
}
}
And refactor the DataReports accordingly
public class DataReports : IDataReports {
private readonly ITaskProviderFactory factory;
public DataReports(ITaskProviderFactory factory) {
this.factory = factory;
}
private ITaskEnumerableProvider getTaskProvider() {
return factory.GetTaskProvider();
}
public IEnumerable<SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders> SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders() {
return getTaskProvider().SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders().ToList();
}
}
At run-time in production, the appropriate implementation can be explicitly injected.
For an integration test of DataReports the actual implementations can be swapped out as needed in order to verify the expected behavior.
For example
[TestMethod]
public void SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrdersTest() {
//Arrange
var options = new DbContextOptionsBuilder<DAL_EFCore.AdventureWorks2017Context>()
.UseInMemoryDatabase(databaseName: "SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrdersTest")
.Options;
using (var context = new DAL_EFCore.AdventureWorks2017Context(options)) {
InsertData(options);
}
using (var context = new DAL_EFCore.AdventureWorks2017Context(options)) {
//actual EFCoreTaskProvider.Tasks targeting in-memory database
var taskProvider = new EFCoreTaskProvider.Tasks(context);
//mock factory configured to return the desired provider
var mockFactory = Mock.Of<ITaskProviderFactory>(_ =>
_.GetTaskProvider() == taskProvider //return the actual provider for testing
);
// actual CoreReportService.DataReports (Subject under test)
var dataReports = DataReports(mockFactory);
//Act
var result = dataReports.SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders().ToList();
//Assert
Assert.IsTrue(result.Count == 1);
}
}
The original design of your classes is not very flexible so it makes it difficult to isolate parts for testing.
I am trying to implement an asp.net web API and it is my first time trying to implement such a project. I googled a lot.
Many times lost down the road because there is a lot of misguiding articles. Now I accomplished to implement the layered architecture. I have a business entity, business service, data models class library projects and a web API itself. As I said I googled a lot since I have no experience with web API, I decided to use generic repository and unit of work in my solution. My API is working butI get the following error when I load test with 400 users:
System.Transactions.TransactionException: The operation is not valid for the state of the transaction. ---> System.TimeoutException: Transaction Timeout. The underlying provider failed on Open.
I researched and asked experienced friends, they said I am not disposing of all the DBContexts I am creating. As I am using transient scope, a new DBcontext is created for every action call (controller construction), but unless I call context.Dispose() in the action the pools are not released.
Honestly, I am not sure how can I dispose of more.
Controller:
namespace Game.Controllers
{
[System.Web.Http.RoutePrefix("api/v2/game")]
public class GameController : ApiController
{
private readonly IGameServices _gameServices;
#region Public Constructor
public GameController(IGameServices gameServices)
{
_gameServices = gameServices;
}
#endregion
[HttpPost, Route("purchase")]
public async Task<IHttpActionResult> PurchaseGame(RequestDto game)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
//Call Services
var gameConfirmResponse = await _gameServices.GamePurchase(game);
switch (gameConfirmResponse.StatusCode)
{
case HttpStatusCode.NotFound:
{
return NotFound();
}
case HttpStatusCode.InternalServerError:
{
return InternalServerError();
}
case HttpStatusCode.OK:
{
if (gameConfirmResponse.Content == null)
{
return new System.Web.Http.Results.ResponseMessageResult(
Request.CreateErrorResponse((HttpStatusCode) 222, new HttpError("No Results Found")));
}
var responseStream = await gameConfirmResponse.Content.ReadAsStringAsync();
var resultResponse = JsonConvert.DeserializeObject<GameConfirmResponse>(responseStream);
if (resultResponse.coupons == null)
{
return new System.Web.Http.Results.ResponseMessageResult(
Request.CreateErrorResponse((HttpStatusCode) 222,
new HttpError("No Coupons Available for this Game")));
}
//Transform GameConfirmResponse into DTO for returning result
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<GameConfirmResponse, GameConfirmResponseDto>();
cfg.CreateMap<Coupon, CouponDto>();
});
var iMapper = config.CreateMapper();
var resultDto = iMapper.Map<GameConfirmResponse, GameConfirmResponseDto>(resultResponse);
return Ok(resultDto);
}
case HttpStatusCode.Unauthorized:
{
return Unauthorized();
}
case HttpStatusCode.RequestTimeout:
{
return InternalServerError();
}
}
return Ok(gameConfirmResponse);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
}
base.Dispose(disposing);
}
}
}
Service:
namespace BusinessService
{
public class GameServices : IGameServices, IDisposable
{
private readonly UnitOfWork _unitOfWork;
public GameServices(UnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public async Task<HttpResponseMessage> GamePurchase(RequestDto requestDto)
{
HttpResponseMessage response = null;
response = await CallRazerService(requestDto);
return response;
}
private async Task<HttpResponseMessage> CallRazerService(RequestDto requestDto)
{
HttpResponseMessage response = null;
using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
//Added to see if I fix the error
TransactionInterop.GetTransmitterPropagationToken(Transaction.Current);
//Transform DTO into GameRequest for calling Razer Initiate
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<RequestDto, GameRequest>();
cfg.CreateMap<GameRequest, GameConfirmRequest>();
cfg.CreateMap<GameConfirmResponse, GameConfirmResponseDto>();
cfg.CreateMap<Coupon, CouponDto>();
cfg.CreateMap<GameRequest, GameRequestDto>();
});
var iMapper = config.CreateMapper();
var gameRequest = iMapper.Map<RequestDto, GameRequest>(requestDto);
//Unique reference ID
gameRequest.referenceId = Guid.NewGuid();
var gameRequestDto = iMapper.Map<GameRequest, GameRequestDto>(gameRequest);
//Create signature
gameRequest = Utilities.CreateSignature(gameRequestDto, RequestType.Initiate);
//Set service
gameRequest.service = "RAZER";
//Add initiation request into database
_unitOfWork.GameRepository.Insert(gameRequest);
#region Call Razer initiate/confirm
//Call Razer for initiation
response = await Utilities.CallRazer(gameRequest, "purchaseinitiation");
//Read response
var htmlResponse = await response.Content.ReadAsStringAsync();
var gameResponse = JsonConvert.DeserializeObject<GameResponse>(htmlResponse);
//Adding initiation response into database
_unitOfWork.GameResponseRepository.Insert(gameResponse);
if (gameResponse.initiationResultCode == "00")
{
gameRequestDto = iMapper.Map<GameRequest, GameRequestDto>(gameRequest);
gameRequestDto.validatedToken = gameResponse.validatedToken;
//Create signature
var gameConfirmRequest = Utilities.CreateSignature(gameRequestDto, RequestType.Confirm);
//Transform DTO into GameRequest for calling Razer Initiate
var gameConfirmRequests = iMapper.Map<GameRequest, GameConfirmRequest>(gameConfirmRequest);
//Add confirm request into database
_unitOfWork.GameConfirmRequestRepository.Insert(gameConfirmRequests);
//Call Razer for confirm
response = await Utilities.CallRazer(gameConfirmRequest, "purchaseconfirmation");
//Read response
htmlResponse = await response.Content.ReadAsStringAsync();
var gameConfirmResponse = JsonConvert.DeserializeObject<GameConfirmResponse>(htmlResponse);
//Set service
gameConfirmResponse.service = "RAZER";
//Add confirm response into database
_unitOfWork.GameConfirmResponseRepository.Insert(gameConfirmResponse);
}
#endregion
await _unitOfWork.SaveAsync();
scope.Complete();
}
return response;
}
public void Dispose()
{
_unitOfWork.Dispose();
}
}
}
Generic Repo (shortened):
namespace DataModels
{
public class GenericRepository<TEntity> where TEntity : class
{
internal GameContext context;
internal DbSet<TEntity> dbSet;
public GenericRepository(GameContext context)
{
this.context = context;
this.dbSet = context.Set<TEntity>();
}
#region Public member methods...
public virtual async Task<List<TEntity>> GetGamesAsync(
Expression<Func<TEntity, bool>> where)
{
return await dbSet.Where(where).ToListAsync();
}
public virtual async Task<List<T>> GetGameBankProducts<T, TType>(Expression<Func<TEntity, bool>> where,
Expression<Func<TEntity, TType>> groupBy, Expression<Func<IGrouping<TType, TEntity>, T>> select)
{
return await dbSet.Where(where)
.GroupBy(groupBy)
.Select(select)
.ToListAsync();
}
#endregion
}
}
Unit of Work:
namespace DataModels
{
public class UnitOfWork : IDisposable
{
private readonly GameContext _context;
private readonly GenericRepository<GameRequest> gameRequestRepository;
private readonly GenericRepository<GameResponse> gameResponseRepository;
private readonly GenericRepository<GameConfirmRequest> gameConfirmRequestRepository;
private readonly GenericRepository<GameConfirmResponse> gameConfirmResponseRepository;
private readonly GenericRepository<GameBank> gameBankRepository;
private readonly GenericRepository<GameBankPin> gameBankPinRepository;
private readonly GenericRepository<ConfirmCancel> confirmCancelRepository;
private readonly GenericRepository<Recon> reconRepository;
private readonly GenericRepository<Company> userRepository;
public UnitOfWork(GameContext context)
{
_context = context;
}
public GenericRepository<GameRequest> GameRepository
{
get
{
return this.gameRequestRepository ?? new GenericRepository<GameRequest>(_context);
}
}
public GenericRepository<GameResponse> GameResponseRepository
{
get
{
return this.gameResponseRepository ?? new GenericRepository<GameResponse>(_context);
}
}
public GenericRepository<GameConfirmRequest> GameConfirmRequestRepository
{
get
{
return this.gameConfirmRequestRepository ?? new GenericRepository<GameConfirmRequest>(_context);
}
}
public GenericRepository<GameConfirmResponse> GameConfirmResponseRepository
{
get
{
return this.gameConfirmResponseRepository ?? new GenericRepository<GameConfirmResponse>(_context);
}
}
public GenericRepository<GameBank> GameBankRepository
{
get
{
return this.gameBankRepository ?? new GenericRepository<GameBank>(_context);
}
}
public GenericRepository<GameBankPin> GameBankPinRepository
{
get
{
return this.gameBankPinRepository ?? new GenericRepository<GameBankPin>(_context);
}
}
public GenericRepository<ConfirmCancel> ConfirmCancelRepository
{
get
{
return this.confirmCancelRepository ?? new GenericRepository<ConfirmCancel>(_context);
}
}
public GenericRepository<Recon> ReconRepository
{
get
{
return this.reconRepository ?? new GenericRepository<Recon>(_context);
}
}
public GenericRepository<Company> UserRepository
{
get
{
return this.userRepository ?? new GenericRepository<Company>(_context);
}
}
public void Save()
{
_context.SaveChanges();
}
public async Task SaveAsync()
{
await _context.SaveChangesAsync();
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
_context.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
Unity config:
namespace Game
{
public static class UnityConfig
{
#region Unity Container
private static Lazy<IUnityContainer> container =
new Lazy<IUnityContainer>(() =>
{
var container = new UnityContainer();
RegisterTypes(container);
return container;
});
public static IUnityContainer Container => container.Value;
#endregion
public static void RegisterTypes(IUnityContainer container)
{
// Default empty Time manager!
container.RegisterType<IGameServices, GameServices>().RegisterType<UnitOfWork>(new TransientLifetimeManager());
container.RegisterType<IUserValidate, UserValidate>(new TransientLifetimeManager());
}
}
}
People keep telling me the best practice is to use "using" but how can I use using in the controller for the Dbcontext? I couldn't find a way.
Update1:
I have been told that instead of transaction scope, I should use dbcontext transaciton (using(var dbContextTransaction = context.Database.BeginTransaction())). I couldn't find a way to use dbcontext transaction in my Service class. I am injecting unit of work in Service class.
Update2:
Should I implement inside of unit of work and remove transactionscope inside of the Service?
UOW Save:
public void Save(){
try{
using (var transaction = _context.Database.BeginTransaction()){
try
{
_context.SaveChanges();
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}
}
}catch (Exception e)
{//TODO:Logging}}
I have an ASP.NET WEB API project that using the repository and unit of work patterns along with UnityContainer. I registered all my repositories and unit of work classes with PerRequestLifeTimeManager and everything is working great. Every client request to server work in a single transaction separated from other client requests. After the request is returned to the client, the DbContext is disposed.
My problem begins when I need to to perform heavy actions after I return a response to the client. The DbContext is disposed and I can't request the Unity Container for a new instance (Since no HttpContext exist ).
I read this post PerRequestLifetimeManager can only be used in the context of an HTTP request
And tried to implement 2 different UnityContainer
WebContainer - Register all classes using PerRequestLifeTimeManager
CorContainer - Register all classes using PerResolveLifetimeManager
I thought I solved the problem but I noticed that each repository I request from the CoreContainer using a different DbContext. Also the UnitOfWork has a different DbContext. This off course causing many error in my code.
This is the code I'm using to register my entities in UnityContainer. I have 2 Databases so some of the repository using the DbContext of the first database, and the other using the second DbContext. My UnitOfWork using both DbContexts
public static class UnityConfig
{
#region Unity Container
private static Lazy<IUnityContainer> perRequestContainer =
new Lazy<IUnityContainer>(() =>
{
var container = new UnityContainer();
RegisterCommonTypes<PerRequestLifetimeManager>(container);
return container;
});
private static Lazy<IUnityContainer> perResolveContainer =
new Lazy<IUnityContainer>(() =>
{
var container = new UnityContainer();
RegisterCommonTypes<PerResolveLifetimeManager>(container);
//CNSDeployerService can
container.RegisterType<ICNSDeployerService, CNSDeployerService>(new PerResolveLifetimeManager());
return container;
});
public static IUnityContainer WebContainer => perRequestContainer.Value;
public static IUnityContainer CoreContainer => perResolveContainer.Value;
#endregion
/// <summary>
/// Please notice this configuration exist only inside the scope of a single request
/// See UnityWebApiActivator
/// </summary>
/// <param name="container"></param>
public static void RegisterCommonTypes<T>(IUnityContainer container) where T: LifetimeManager
{
container.RegisterType<ApplicationDbContext, ApplicationDbContext>(Activator.CreateInstance<T>());
container.RegisterType<DbContext, ApplicationDbContext>(Activator.CreateInstance<T>());
container.RegisterType<capNsubContext, capNsubContext>(Activator.CreateInstance<T>());
container.RegisterType<IDataContextAsync, capNsubContext>("capNsubContext", Activator.CreateInstance<T>());
container.RegisterType<IDataContextAsync, LinetServerContext>("linnetDataContext", Activator.CreateInstance<T>());
container.RegisterType<IRepository<Entity>, Repository<Entity>>(Activator.CreateInstance<T>());
container.RegisterType<IRepositoryAsync<VideoTargetLanguage>, Repository<VideoTargetLanguage>>(Activator.CreateInstance<T>());
//Unity understand array by defaults so we just need to map it to IEnumerable
container.RegisterType<IEnumerable<IDataContextAsync>, IDataContextAsync[]>();
container.RegisterType<IUnitOfWorkAsync, UnitOfWork>(Activator.CreateInstance<T>());
container.RegisterType<UserManager<ApplicationUser>>(Activator.CreateInstance<T>());
container.RegisterType<RoleManager<IdentityRole>>(Activator.CreateInstance<T>());
container.RegisterType<AccountController>(Activator.CreateInstance<T>());
container.RegisterType<IUserStore<ApplicationUser>, UserStore<ApplicationUser>>(Activator.CreateInstance<T>());
container.RegisterType<IOrderService, OrderService>(Activator.CreateInstance<T>());
container.RegisterType<IFFMpegService, FFMpegService>(Activator.CreateInstance<T>());
container.RegisterType<IVideoService, VideoService>(Activator.CreateInstance<T>());
container.RegisterType<IOrderItemService, OrderItemService>(Activator.CreateInstance<T>());
container.RegisterType<ILanguageService, LanguageService>(Activator.CreateInstance<T>());
container.RegisterType<IUserService, UserService>(Activator.CreateInstance<T>());
container.RegisterType<ICNSCaptionsService, CNSCaptionsService>(Activator.CreateInstance<T>());
container.RegisterType<ICNSTranslationsService, CNSTranslationsService>(Activator.CreateInstance<T>());
container.RegisterType<ICNSCapMoviesService, CNSMovieService>(Activator.CreateInstance<T>());
container.RegisterType<HttpClient, HttpClient>(Activator.CreateInstance<T>());
container.RegisterType<SimpleRefreshTokenProvider, SimpleRefreshTokenProvider>(Activator.CreateInstance<T>());
var capNsubEntityTypes = GetEntityFrameworkEntityTypesByContext<capNsubContext>();
var linnetEntityTypes = GetEntityFrameworkEntityTypesByContext<LinetServerContext>();
RegisterEntitiesRepostiories<T>(container, capNsubEntityTypes, "capNsubContext");
RegisterEntitiesRepostiories<T>(container, linnetEntityTypes, "linnetDataContext");
}
private static void RegisterEntitiesRepostiories<T>(IUnityContainer container, IEnumerable<Type> entities, string contextName)
where T:LifetimeManager
{
var iGenericRepositoryTypes = new[] { typeof(IRepositoryAsync<>), typeof(IRepository<>) };
foreach (var iGenericRepositoryType in iGenericRepositoryTypes)
{
foreach (var entityType in entities)
{
var iSpecificRepositoryType = iGenericRepositoryType.MakeGenericType(entityType);
var genericRepositoryType = typeof(Repository<>);
var specificRepositoryType = genericRepositoryType.MakeGenericType(entityType);
container.RegisterType(iSpecificRepositoryType, Activator.CreateInstance<T>(), new InjectionFactory(c =>
{
return Activator.CreateInstance(specificRepositoryType, c.Resolve<IDataContextAsync>(contextName), c.Resolve<IUnitOfWorkAsync>());
}));
}
}
}
private static IEnumerable<Type> GetEntityFrameworkEntityTypesByContext<T>() where T : DataContext
{
var capNsubContextType = typeof(T);
var capNsubDataAssembly = Assembly.GetAssembly(capNsubContextType);
var ef6EntityType = typeof(Repository.Pattern.Ef6.Entity);
return capNsubDataAssembly.GetTypes()
.Where(t => String.Equals(t.Namespace, capNsubContextType.Namespace, StringComparison.Ordinal) &&
t.IsSubclassOf(ef6EntityType));
}
}
[System.Web.Http.Authorize(Roles = "admin")]
[System.Web.Http.RoutePrefix("api/job")]
public class JobController : BaseApiController {
[System.Web.Http.Route("Create", Name = "Create")]
[System.Web.Http.HttpPost]
public IHttpActionResult Create(JobBindingModel createJobModal)
{
//We have to use the CoreContainer since cnsDeployer scope runs outside of the request
var cnsDeployer = UnityConfig.CoreContainer.Resolve<ICNSDeployerService>();
if (!ModelState.IsValid)
{
return BadRequest();
}
try
{
//This runs in the backround after we return the response to client
cnsDeployer.Deploy(createJobModal.ItemIds);
return Ok();
}
catch(Exception err)
{
return InternalServerError(err);
}
}
}
public class CNSDeployerService : ICNSDeployerService
{
private readonly IOrderItemService orderItemService;
private readonly ICNSCapMoviesService cnsMoviesService;
private readonly ICNSTranslationsService cnsTranslationsService;
private readonly IFFMpegService ffMpegService;
private readonly IUnitOfWorkAsync unitOfWorkAsync;
private readonly IVideoService videoService;
public CNSDeployerService(IOrderItemService orderItemService,
ICNSCapMoviesService cnsCapMoviesService,
ICNSTranslationsService cnsTranslationsService,
IFFMpegService ffMpegService,
IUnitOfWorkAsync unitOfWorkAsync,
IVideoService videoService)
{
this.orderItemService = orderItemService;
this.cnsMoviesService = cnsCapMoviesService;
this.cnsTranslationsService = cnsTranslationsService;
this.ffMpegService = ffMpegService;
this.unitOfWorkAsync = unitOfWorkAsync;
this.videoService = videoService;
}
public void Deploy(IEnumerable<Guid> orderItemIds)
{
try
{
InnerDeploy(orderItemIds);
}
catch
{
unitOfWorkAsync.Dispose();
}
}
private void InnerDeploy(IEnumerable<Guid> orderItemIds)
{
var orderItems = orderItemService.Queryable()
.Where(orderItem => orderItemIds.Any(itemId => orderItem.Id == itemId)
&& orderItem.IsInProcessQueue == false
&& !orderItem.TranslationId.HasValue)
.ToList();
if (orderItems.Count == 0)
{
unitOfWorkAsync.Dispose();
throw new ArgumentNullException("No valid orders was provided");
}
foreach ( var orderItem in orderItems)
{
orderItem.IsInProcessQueue = true;
orderItemService.Update(orderItem);
}
unitOfWorkAsync.SaveChanges();
var translationId = Guid.NewGuid();
var movieId = Guid.NewGuid();
var connectedMoviePath = cnsMoviesService.GetMoviePath(movieId);
var videosUrlList = orderItems
.Select(orderItem => orderItem.VideosTable.VideoUrl)
.ToList();
//Don't await on this task since we want concat to continue after request is returned
Task.Run(async () =>
{
try
{
await ffMpegService.ConcatVideos(videosUrlList, connectedMoviePath);
VideoUtils.CreateVideoImageAndReturnPath(connectedMoviePath);
var videosTotalDuration = videoService.GetVideosTotalDuration(orderItemIds);
var durationInSeconds = Convert.ToInt32((int)(videosTotalDuration / 1000));
await cnsMoviesService.CreateMovieRecordAsync(movieId, durationInSeconds);
await cnsTranslationsService.CreateTranslationRecordAsync(movieId, translationId, language: 1);
var index = 0;
foreach (var orderItem in orderItems)
{
orderItem.TranslationId = translationId;
orderItem.TranslationIndex = index++;
orderItem.IsInProcessQueue = false;
orderItemService.Update(orderItem);
}
await unitOfWorkAsync.SaveChangesAsync();
}
catch (Exception err)
{
//TODO: Handle error
}
finally
{
//Dispose db context
unitOfWorkAsync.Dispose();
}
});
}
}
I found a decent article to get me started on unit testing my Entity Framework-based application using Moq: https://msdn.microsoft.com/en-us/data/dn314429.aspx
This issue I'm having is that the SaveChanges method of the Mock does not appear to trigger the ValidateEntity method like it normally would. None of the validation settings I configured in the EntityTypeConfiguration are being thrown as a DbEntityValidationException.
For example, my AddRoles_Fails_For_Empty_Name tests to make sure that the service cannot add a role with an empty name. Either the IsRequired() configuration is not being applied, or the ValidateEntity method is not being called. I should mention that it works correctly if I use the actual context in the web app.
I've included some of my relevant unit testing, DbContext, and Service code below.
Am I doing something incorrectly? Are there any known issues or workarounds?
Role DB Map
public class RoleMap : EntityTypeConfiguration<Role>
{
public RoleMap()
{
ToTable("bm_Roles");
HasKey(r => r.Id);
Property(r => r.Name).IsRequired().HasMaxLength(100).HasIndex(new IndexAttribute("UX_Role_Name") { IsUnique = true });
Property(r => r.Description).HasMaxLength(500);
}
}
DbContext
public class BlueMoonContext : DbContext, IBlueMoonContext
{
public BlueMoonContext() : base("name=BlueMoon")
{
}
public DbSet<Role> Roles { get; set; }
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.AddFromAssembly(typeof(BlueMoonContext).Assembly);
}
public void MarkAsModified<T>(T entity) where T : class
{
entity.ThrowIfNull("entity");
Entry<T>(entity).State = EntityState.Modified;
}
protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items)
{
var result = base.ValidateEntity(entityEntry, items);
if (entityEntry.State == EntityState.Added || entityEntry.State == EntityState.Modified)
{
// Perform validations that require database lookups
if (entityEntry.Entity is Role)
{
ValidateRole((Role)entityEntry.Entity, result);
}
else if (entityEntry.Entity is User)
{
ValidateUser((User)entityEntry.Entity, result);
}
}
return result;
}
private void ValidateRole(Role role, DbEntityValidationResult result)
{
if (role.Name.HasValue() && !Roles.NameAvailable(role.Name, role.Id))
{
result.ValidationErrors.Add(new DbValidationError("Name", "Already in use"));
}
}
private void ValidateUser(User user, DbEntityValidationResult result)
{
if (user.UserName.HasValue() && !Users.UserNameAvailable(user.UserName, user.Id))
{
result.ValidationErrors.Add(new DbValidationError("UserName", "Already in use"));
}
if (user.Email.HasValue() && !Users.UserNameAvailable(user.UserName, user.Id))
{
result.ValidationErrors.Add(new DbValidationError("Email", "Already in use"));
}
}
}
Account Service
public class AccountService : BaseService, IAccountService
{
private IPasswordHasher _passwordHasher;
public AccountService(IBlueMoonContext context, IPasswordHasher passwordHasher) : base(context)
{
_passwordHasher = passwordHasher;
}
public ServiceResult CreateRole(Role role)
{
role.ThrowIfNull("role");
Context.Roles.Add(role);
return Save();
}
// Copied from base service class
protected ServiceResult Save()
{
var result = new ServiceResult();
try
{
Context.SaveChanges();
}
catch (DbEntityValidationException validationException)
{
foreach (var validationError in validationException.EntityValidationErrors)
{
foreach (var error in validationError.ValidationErrors)
{
result.AddError(error.ErrorMessage, error.PropertyName);
}
}
}
return result;
}
}
Unit Test
[TestFixture]
public class AccountServiceTests : BaseTest
{
protected Mock<MockBlueMoonContext> _context;
private IAccountService _accountService;
[TestFixtureSetUp]
public void Setup()
{
_context = new Mock<BlueMoonContext>();
var data = new List<Role>
{
new Role { Id = 1, Name = "Super Admin" },
new Role { Id = 2, Name = "Catalog Admin" },
new Role { Id = 3, Name = "Order Admin" }
}.AsQueryable();
var roleSet = CreateMockSet<Role>(data);
roleSet.Setup(m => m.Find(It.IsAny<object[]>())).Returns<object[]>(ids => data.FirstOrDefault(d => d.Id == (int)ids[0]));
_context.Setup(m => m.Roles).Returns(roleSet.Object);
// _context.Setup(m => m.SaveChanges()).Returns(0);
_accountService = new AccountService(_context.Object, new CryptoPasswordHasher());
}
[Test]
public void AddRole_Fails_For_Empty_Name()
{
var role = new Role { Id = 4, Name = "" };
var result = _accountService.CreateRole(role);
Assert.False(result.Success);
}
}
SaveChanges is a virtual method which means you invoke a fake method....
You can create your mock CallBase = true, but it is not a good idea(it miss the idea of UT):
_context = new Mock<BlueMoonContext>(){ CallBase = true };
The above code will use the real implementation of BlueMoonContext for any method/property which is not explicitly setup.
RoleMap is responsible for your DB stracture, you should test it as a part of integration test(with DB).
In my opinion you should create an integration tests to verify the integrity(for example; cover RoleMap) with your DB, And create a UT using the Throw setup to cover the catch section(it's a part of your unit):
_contest.Setup(x => x.SaveChanges())
.Throws(new DbEntityValidationException());
Edit to answer the OP question in the comment
no, you don't have to separate the built in validation, you have to create another test(integration test). In this test you'll verify the validation behaviour: insert an illegal entity, expect that exception will raise(using ExpectedExceptionAttribute) and then verify that the DB is empty... to apply this behaviour use this pattern:
try
{
\\...
\\try to commit
}
catch(DbEntityValidationException ex)
{
\\do some validation, then:
throw;\\for ExpectedExceptionAttribute
}
I looked over the api of EntityTypeConfiguration, I didn't saw any contact which allows to UT the rules(unless you use tools like MsFakes, TypeMock Isolator there is no way to verify the ToTable/HasKey/Property was called). The class is being in used inside EntityFramework(which is a part of the BCL) in the integration test you don't have to verify that EntityFramework work properly, you are going to verify that your custom rules was integrated and works as you expect(In this answer you can read the reason not to test a BCL classes).
So use Moq in the UTs of AccountService. Create an integration tests for BlueMoonContext and RoleMap(without Moq).
By the way #LadislavMrnka offer an interesting way to test(integration test) EntityTypeConfiguration