xUnit, Moq with UnitOfWork and general repository - repository

I tried a lot of examples but I do not get the good response.
The ReportUpload method creates some Report entity based on the list from ExcelManager list
and adds them to the Reports DbSet.
My goal would be to read out the added entities from the mocked DbSet and assert them.
How can I pass the mocked DbContext and Dbset into UnitOfWork, please?
GeneralRepository.cs
public class GeneralRepository<TContext, TEntity> : IGeneralRepository<TEntity>
where TEntity : class
where TContext : DbContext
{
protected readonly TContext _context;
protected readonly DbSet<TEntity> dbSet;
public GeneralRepository(TContext context)
{
_context = context;
dbSet = _context.Set<TEntity>();
}
public async Task AddAsync(TEntity entity)
{
await _context.Set<TEntity>().AddAsync(entity);
}
...
}
IUnitOfWork.cs
public interface IUnitOfWork : IDisposable
{
IGeneralRepository<Report> Reports{ get; }
...
}
UnitOfWork.cs
public sealed class UnitOfWork : IUnitOfWork
{
private readonly MyDbContext _context;
public UnitOfWork(MyDbContext context)
{
_context = context;
}
private IGeneralRepository<Report>_report;
public IGeneralRepository<Report> Reports => _report ??= new GeneralRepository<MyDbContext, Report>(_context);
...
}
ReportService.cs
public class ReportService : GeneralService, IReportService
{
private readonly IExcelManager _excelManager;
public ReportService(IUnitOfWork unitOfWork, IExcelManager excelManager)
{
UnitOfWork = unitOfWork;
_excelManager = excelManager;
}
public async Task<string> ReportUpload(MemoryStream ms)
{
var workingList = _excelManager.ReadExcel(ms);
var i = 0;
while (i < workingList.Count)
{
var report = new Report { ... }
await UnitOfWork.Reports.AddAsync(report);
}
....
}
ReportServiceTest.cs
public class ReportServiceTests
{
[Fact()]
public async Task ReportUploadTest()
{
//Arrange
....
var mockSet = new Mock<DbSet<Report>>();
var mockContext = new Mock<MyDbContext>();
mockContext.Setup(x => x.Reports).Returns(mockSet.Object);
var reportRepositoryMock = new Mock<IGeneralRepository<Report>>();
reportRepositoryMock.Setup(m => m.AddAsync(It.IsAny<Report>()));
var unitOfWorkMock = new Mock<IUnitOfWork>();
unitOfWorkMock.Setup(p => p.Reports)
.Returns(reportRepositoryMock.Object);
...
//Act
var reportService = new ReportService(unitOfWorkMock.Object,exelManagerMock.Object);
await reportService.ReportUpload(new MemoryStream());
//Assert
???
}

DbContext cannot be passed to the UnitOfWork object because its context field is private.
I had to use SQLite in memory to test the GeneralRepository method.
var exelManagerMock = new Mock<IExcelManager>();
exelManagerMock.Setup(p => p.ReadExcel(It.IsAny<MemoryStream>()))
.Returns(listOfExcelReadResult);
var dbFixture = new DatabaseFixture();
var context = dbFixture.CreateContext();
var unitOfWork = new UnitOfWork(context);
//I need some plus data
await unitOfWork.Providers.AddRangeAsync(providers);
await context.SaveChangesAsync();
var reportService = new ReportService(unitOfWork, exelManagerMock.Object);
await reportService.ReportUpload(new MemoryStream(), 2021);
var allReports = await reportService.UnitOfWork.Reports.GetAsync();
Assert.Equal(3, allReports.Count);

Related

Data variable is equal to null, even though there are elements in the database

I'm seeding sql tables with the use of context factory, It shows up and there is an element in mssql, but when I write a controller, I get an empty list with no entries in the table. Could it perhaps be the inheritance that is causing an issue? Classical concert is inherited from Concert.
ConcertController:
public class ConcertController : Controller
{
private readonly AppDbContext _context;
public ConcertController(AppDbContext context)
{
_context = context;
}
public IActionResult Index()
{
var data = _context.Concerts.ToList(); //this variable is null
return View();
}
}
DbContext:
public class AppDbContext : DbContext
{
public virtual DbSet<Concert> Concerts { get; set; }
public virtual DbSet<Ticket> Tickets { get; set; }
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Party>().HasBaseType<Concert>();
modelBuilder.Entity<ClassicalConcert>().HasBaseType<Concert>();
base.OnModelCreating(modelBuilder);
}
}
Data seeding:
var factory = new AppContextFactory();
using var context = factory.CreateDbContext();
var builder = WebApplication.CreateBuilder(args);
await AddData();
async Task AddData()
{
ClassicalConcert mozart;
await context.AddRangeAsync(new[]
{
mozart = new ClassicalConcert()
{
PerformerName = "Some random dude",
TicketsCount= 1000,
PerformanceDate= DateTime.Now,
Location = "Rnd location",
Description = "some rnd desc",
ImageURL = "some https",
VoiceType = Centaurea.Data.Enums.VoiceTypes.Bass,
ConcertName = "Mozarts back",
ComposersName = "Mozart",
}
});
await context.SaveChangesAsync();
}
Context Factory:
public class AppContextFactory : IDesignTimeDbContextFactory<AppDbContext>
{
public AppDbContext CreateDbContext(string[] args = null)
{
var configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
var optionsBuilder = new DbContextOptionsBuilder<AppDbContext>();
optionsBuilder.UseSqlServer(configuration["ConnectionStrings:DefaultConnection"]);
return new AppDbContext(optionsBuilder.Options);
}
}
Picture of the Debuger:

How to convert ODataQueryOptions<DtoType> to ODataQueryOptions<EntityType> to query the underlying storage?

I have a webapi controller using ProductDTO type for clients but the repository is using a Product type.
I would like to use odata on my endpoint. I receive the ODataQueryOptions parameter and I want to pass it to repository (implemented using CosmosDB).
I cant seem to figure out how to convert from ODataQueryOptions<ProductDTO> to ODataQueryOptions<Product>.
[Route("api/[controller]")]
public class ProductsController<ProductsDTO, Product> : ControllerBase
{
IRepository<Product> _repository;
IMapper _mapper;
[HttpGet]
public async Task<ActionResult<IList<ProductDTO>>> Get(ODataQueryOptions<ProductDTO> queryOptions)
{
var mappedQueryOptions = ... // convert 'queryOptions' to ODataQueryOptions<Product> ???
var products = await _repository.Get(mappedQueryOptions);
return Ok(_mapper.Map<IEnumerable<Product>, IEnumerable<ProductDTO>>(products));
}
}
In my aspnetcore service composition I create and inject automapper
var configuration = new MapperConfiguration(cfg =>
{
cfg.AddProfile(new ProductProfile());
cfg.AddExpressionMapping();
});
internal class ProductProfile : Profile
{
public ProductProfile()
{
CreateMap<Product, ProductDto>().ReverseMap();
}
}
I managed to extract the queryoptions filter as a lambda expression Expression<Func<Product, bool>> (using automapper MapExpression) and passed it to repository , that works to a certain extent but I want to get the select , top, skip, etc. as well.
Any suggestions on how that could be done?
can you review the example?
[ODataRouteComponent("api")]
public class UsersController : ODataController
{
#region ctor
private readonly ILogger<UsersController> _logger;
private readonly IMapper _mapper;
private readonly UserManager<AspNetUser> _userManager;
public UsersController(IMapper mapper,
UserManager<AspNetUser> userManager,
ILogger<UsersController> logger)
{
_mapper = mapper;
_userManager = userManager;
_logger = logger;
}
#endregion
[HttpGet]
[EnableQuery(PageSize = 10)]
public async Task<IActionResult> Get(ODataQueryOptions<UserDto> options)
{
var user = await _userManager.GetUserAsync(User);
_logger.LogInformation("{UserUserName} is getting all users", user.UserName);
return Ok(await _userManager.Users.GetAsync(_mapper, options));
}
[HttpGet]
[EnableQuery]
public async Task<IActionResult> Get(int key, ODataQueryOptions<UserDto> options)
{
var user = await _userManager.GetUserAsync(User);
_logger.LogInformation("{UserUserName} is getting user with id {Key}", user.UserName, key.ToString());
return Ok(_userManager.Users.Get(_mapper, options).FirstOrDefault(x => x.Id == key));
}
}
services.AddControllers(opt =>{}).AddOData((opt, _) =>
{
opt.EnableQueryFeatures().AddRouteComponents("api", EdmModelBuilder.GetEdmModelv1());
});
public class MappingProfile : Profile
{
public MappingProfile() : base(nameof(WebUI))
{
CreateMap<AspNetUser, UserDto>();
}
}
internal static class EdmModelBuilder
{
internal static IEdmModel GetEdmModelv1()
{
ODataConventionModelBuilder builder = new();
builder.EnableLowerCamelCase();
#region UserDto
builder.EntitySet<UserDto>(nameof(DataListContext.Users))
.EntityType
.Page(25, 15);
var f5 = builder.Function("Get");
f5.Parameter<int>("key").Required();
f5.ReturnsFromEntitySet<UserDto>(nameof(DataListContext.Users));
#endregion
return builder.GetEdmModel();
}
}

Endpoint is null when accessed in middleware during asp.net core 3.1 integration test

I run integration tests for my asp.net core application, the call passes from multiple middle-wares but stops at one of them which has the following line :
var endpoint = context.Features.Get<IEndpointFeature>()?.Endpoint;
var attribute = endpoint?.Metadata.GetMetadata<AllowAHeader>();
The endpoint is null.
public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup>
where TStartup : class
{
protected override IHostBuilder CreateHostBuilder()
{
var builder = Host.CreateDefaultBuilder()
.ConfigureWebHostDefaults(x =>
{
x.UseStartup<TStartup>().UseTestServer();
});
return builder;
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
builder.ConfigureTestServices(services =>
{
services.RemoveAll<DbContext>();
services.RemoveAll<DbContextOptions>();
foreach (var option in services.Where(s =>
s.ServiceType.BaseType ==
typeof(DbContextOptions)).ToList())
{
services.Remove(option);
}
services.AddDbContext<DbContext>(options =>
{
options.UseInMemoryDatabase("Testing");
});
});
}
}
Here is the test
public class ClientTests : IClassFixture<CustomWebApplicationFactory<TestStartup>>
{
private readonly HttpClient _client;
public ClientTests(CustomWebApplicationFactory<TestStartup> customWebApplicationFactory)
{
_client = customWebApplicationFactory.CreateClient();
}
[Fact]
public async Task GetClients()
{
_client.DefaultRequestHeaders.Add("X-Integration-Testing", "True");
_client.DefaultRequestHeaders.Add("X-Integration-Authroize", "Basic");
var result = await _client.PostAsync("v1/client", null);
}
}
The TestStartup :
public class TestStartup : Startup
{
public TestStartup(IConfiguration configuration)
: base(configuration)
{
}
protected override void ConfigureMiddlewareForIntegrationTest(IApplicationBuilder app)
{
app.UseMiddleware<AuthenticatedTestRequestMiddleware>();
}
}
public class AuthenticatedTestRequestMiddleware
{
public const string TestingHeader = "X-Integration-Testing";
public const string TestingHeaderAuthValueValue = "X-Integration-Authroize";
private readonly RequestDelegate _next;
public AuthenticatedTestRequestMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
if (context.Request.Headers.Keys.Contains(TestingHeader))
{
if (context.Request.Headers.Keys.Contains(TestingHeaderAuthValueValue))
{
var encoded = "Basic " + System.Convert.ToBase64String(System.Text.Encoding.GetEncoding("ISO-8859-1").GetBytes("user" + ":" + "123456"));
context.Request.Headers.Add("Authorization", encoded);
}
}
}
}
In ConfigureWebHostDefaults add:
x.UseHttpSys(opt =>
opt.RequestQueueMode = RequestQueueMode.Create;
)
Have not figured out exactly why it's needed, but I'm guessing it's a bug being the value of RequestQueueMode is 0 by default, same as RequestQueueMode.Create's value.

Unit test exception when calling API service using IHttpContextAccessor [duplicate]

I have a method to get header value using IHttpContextAccessor
public class HeaderConfiguration : IHeaderConfiguration
{
public HeaderConfiguration()
{
}
public string GetTenantId(IHttpContextAccessor httpContextAccessor)
{
return httpContextAccessor.HttpContext.Request.Headers["Tenant-ID"].ToString();
}
}
I am testing GetBookByBookId method
Let's say the method looks like this:
public class Book
{
private readonly IHttpContextAccessor _httpContextAccessor;
private IHeaderConfiguration _headerConfiguration;
private string _tenantID;
public Book(IHeaderConfiguration headerConfiguration, IHttpContextAccessor httpContextAccessor){
var headerConfig = new HeaderConfiguration();
_httpContextAccessor = httpContextAccessor;
_tenantID = headerConfig.GetTenantId(_httpContextAccessor);
}
public Task<List<BookModel>> GetBookByBookId(string id){
//do something with the _tenantId
//...
}
}
Here's my unit test for GetBookByBookId method
[Fact]
public void test_GetBookByBookId()
{
//Arrange
//Mock IHttpContextAccessor
var mockHttpContextAccessor = new Mock<IHttpContextAccessor>();
mockHttpContextAccessor.Setup(req => req.HttpContext.Request.Headers["Tenant-ID"].ToString()).Returns(It.IsAny<string>());
//Mock HeaderConfiguration
var mockHeaderConfiguration = new Mock<IHeaderConfiguration>();
mockHeaderConfiguration.Setup(x => x.GetTenantId(mockHttpContextAccessor.Object)).Returns(It.IsAny<string>());
var book = new Book( mockHttpContextAccessor.Object, mockHeaderConfiguration.Object);
var bookId = "100";
//Act
var result = book.GetBookByBookId(bookId);
//Assert
result.Result.Should().NotBeNull().And.
BeOfType<List<BookModel>>();
}
But for this line:
mockHttpContextAccessor.Setup(req => req.HttpContext.Request.Headers["Tenant-ID"].ToString()).Returns(It.IsAny<string>());
It says
System.NotSupportedException: 'Type to mock must be an interface or an abstract or non-sealed class. '
I was wondering what's the proper way to mock IHttpContextAccessor with header value?
You can use the DefaultHttpContext as a backing for the IHttpContextAccessor.HttpContext. Saves you having to set-up too many things
Next you cannot use It.IsAny<string>() as a Returns result. They were meant to be used in the set up expressions alone.
Check the refactor
[Fact]
public async Task test_GetBookByBookId() {
//Arrange
//Mock IHttpContextAccessor
var mockHttpContextAccessor = new Mock<IHttpContextAccessor>();
var context = new DefaultHttpContext();
var fakeTenantId = "abcd";
context.Request.Headers["Tenant-ID"] = fakeTenantId;
mockHttpContextAccessor.Setup(_ => _.HttpContext).Returns(context);
//Mock HeaderConfiguration
var mockHeaderConfiguration = new Mock<IHeaderConfiguration>();
mockHeaderConfiguration
.Setup(_ => _.GetTenantId(It.IsAny<IHttpContextAccessor>()))
.Returns(fakeTenantId);
var book = new Book(mockHttpContextAccessor.Object, mockHeaderConfiguration.Object);
var bookId = "100";
//Act
var result = await book.GetBookByBookId(bookId);
//Assert
result.Should().NotBeNull().And.
BeOfType<List<BookModel>>();
}
There may also be an issue with the Class Under Test as it is manually initializing the HeaderConfiguration when it should actually be explicitly injected.
public Book(IHeaderConfiguration headerConfiguration, IHttpContextAccessor httpContextAccessor) {
_httpContextAccessor = httpContextAccessor;
_tenantID = headerConfiguration.GetTenantId(_httpContextAccessor);
}
In my scenario I had to mock IHttpContextAccessor and access the inner request url bits.
I'm sharing it here because I spent a decent amount of time figuring this out and hopefully it'll help someone.
readonly Mock<IHttpContextAccessor> _HttpContextAccessor =
new Mock<IHttpContextAccessor>(MockBehavior.Strict);
void SetupHttpContextAccessorWithUrl(string currentUrl)
{
var httpContext = new DefaultHttpContext();
setRequestUrl(httpContext.Request, currentUrl);
_HttpContextAccessor
.SetupGet(accessor => accessor.HttpContext)
.Returns(httpContext);
static void setRequestUrl(HttpRequest httpRequest, string url)
{
UriHelper
.FromAbsolute(url, out var scheme, out var host, out var path, out var query,
fragment: out var _);
httpRequest.Scheme = scheme;
httpRequest.Host = host;
httpRequest.Path = path;
httpRequest.QueryString = query;
}
}
If you are making use of the wonderful NSubstitute package for NUnit, you can do this...
var mockHttpAccessor = Substitute.For<IHttpContextAccessor>();
var context = new DefaultHttpContext
{
Connection =
{
Id = Guid.NewGuid().ToString()
}
};
mockHttpAccessor.HttpContext.Returns(context);
// usage...

A second operation started on this context before a previous operation completed

I have a project with asp.net core and entity framework core, for performance reasons I use MemoryCache. ForumQueryManager class is for querying forum data. This class for data uses the CacheManager Get method and passes cache key and timeout cache time and a method for when the cache is empty for retrieving data from the database. this code work almost always. but sometimes throw an exception
Exception:
An unhandled exception occurred while processing the request.
InvalidOperationException: A second operation started on this context
before a previous operation completed. Any instance members are not
guaranteed to be thread safe.
Microsoft.EntityFrameworkCore.Internal.ConcurrencyDetector.EnterCriticalSection()
ForumQueryManager:
public class ForumQueryManager : IForumQueryManager
{
private readonly ApplicationDbContext _dbContext;
private readonly ICalender _calender;
private readonly ICacheManager _cacheManager;
public ForumQueryManager(ApplicationDbContext dbContext, ICacheManager cacheManager)
{
_dbContext = dbContext;
_cacheManager = cacheManager;
}
public async Task<List<ForumCategoryDto>> GetAll()
{
var items = await _cacheManager.Get(CacheConstants.ForumCategories, 20, GetForumCategories);
return items;
}
private async Task<List<ForumCategoryDto>> GetForumCategories()
{
var categories = await _dbContext.ForumCategories
.Select(e => new ForumCategoryDto
{
Name = e.Name,
ForumCategoryId = e.ForumCategoryId
}).ToListAsync();
return categories;
}
}
CacheManager:
public class CacheManager: ICacheManager
{
private readonly IMemoryCache _cache;
private readonly CacheSetting _cacheSetting;
public CacheManager(IMemoryCache cache, IOptions<CacheSetting> cacheOption)
{
_cache = cache;
_cacheSetting = cacheOption.Value;
}
public async Task<List<T>> Get<T>(string cacheKey, int expirationMinutes, Func<Task<List<T>>> function)
{
List<T> items;
if (_cacheSetting.MemeoryEnabled)
{
var value = _cache.Get<string>(cacheKey);
if (value == null)
{
items = await function();
value = JsonConvert.SerializeObject(items, Formatting.Indented,
new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
MissingMemberHandling = MissingMemberHandling.Ignore,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});
_cache.Set(cacheKey, value,
new MemoryCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromMinutes(expirationMinutes)));
}
else
{
items = JsonConvert.DeserializeObject<List<T>>(value);
}
}
else
{
items = await function();
}
return items;
}
}
ForumQueryManager must be transient, otherwise the _dbContext variable will be reused.