How to access repository from IRouter - entity-framework-core

I'm developing modular application and I'd like for entities from different modules to be able to register their own friendly url slugs.
app.UseMvc(routes =>
{
routes.Routes.Add(new SlugRouter(routes.DefaultHandler));
(...)
});
But following code throws Cannot access a disposed object. Object name: 'CommerceDbContext'. when trying to access slug from the repository.
public class SlugRouter : IRouter
{
private readonly IRouter _target;
public SlugRouter(IRouter target)
{
_target = target;
}
public async Task RouteAsync(RouteContext context)
{
var slugRepository = context.HttpContext.RequestServices.GetService<IRepository<SlugEntity>>();
// ERROR: Cannot access a disposed object. Object name: 'CommerceDbContext'
var urlSlug = await slugRepository.GetAllIncluding(x => x.EntityType).FirstOrDefaultAsync(x => x.Slug == context.HttpContext.Request.Path.Value);
(...)
}
It must be something simple I'm missing to be able to access the repository from router. Thanks for any help.

Begin a unit of work:
public async Task RouteAsync(RouteContext context)
{
var slugRepository = context.HttpContext.RequestServices.GetService<IRepository<SlugEntity>>();
var unitOfWorkManager = context.HttpContext.RequestServices.GetService<IUnitOfWorkManager>();
using (var uow = unitOfWorkManager.Begin())
{
var urlSlug = await slugRepository.GetAllIncluding(x => x.EntityType).FirstOrDefaultAsync(x => x.Slug == context.HttpContext.Request.Path.Value);
await uow.CompleteAsync();
}
}

Access IModel. You do not need dbContext for.
for entities from different modules to be able to register their own
friendly url slugs
I do it this way:
1) move OnModelCreating to static methiod
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
BuildModel(modelBuilder);
}
public static void BuildModel(ModelBuilder modelBuilder)
{
// ...
}
2) Create model where you need:
var conventionSet = new ConventionSet();
var modelBuilder = new ModelBuilder(conventionSet);
AdminkaDbContext.BuildModel(modelBuilder);
var mutableModel = modelBuilder.Model;
There is your meta (in mutableModel ). You can loop through entities (types of entities).

Related

Replace SQLite to LocalDb for unittesting for a ABP.io project

I am working with ABP and I used the basic setup which is generated by the abp cli with EF Core and SQL Server.
Now, I would like to replace SQLite (the generated one), with a LocalDb database, since I using spatial data for unit- and integration testing.
However, I am not sure how to make the replacement. I have identified the file which initialize the SQLite, and it is reseeded after each unittest.
When switching to LocalDb, I will not flush / seed database, and I think I have solved that out.
This is the generated file ...\ABPProject\aspnet-core\ABPProject.EntityFrameworkCore.Tests\EntityFrameworkCore\ABPProjectEntityFrameworkCoreTestModule.cs that uses SQLite, can you please give me an example / ideas how to use LocalDb instead of SQLite?
[DependsOn(
typeof(ABPProjectEntityFrameworkCoreModule),
typeof(ABPProjectTestBaseModule),
typeof(AbpEntityFrameworkCoreSqliteModule)
)]
public class ABPProjectEntityFrameworkCoreTestModule : AbpModule
{
private SqliteConnection _sqliteConnection;
public override void ConfigureServices(ServiceConfigurationContext context)
{
ConfigureInMemorySqlite(context.Services);
}
private void ConfigureInMemorySqlite(IServiceCollection services)
{
_sqliteConnection = CreateDatabaseAndGetConnection();
services.Configure<AbpDbContextOptions>(options =>
{
options.Configure(context =>
{
context.DbContextOptions.UseSqlite(_sqliteConnection);
});
});
}
public override void OnApplicationShutdown(ApplicationShutdownContext context)
{
_sqliteConnection.Dispose();
}
private static SqliteConnection CreateDatabaseAndGetConnection()
{
var connection = new SqliteConnection("Data Source=:memory:");
connection.Open();
var options = new DbContextOptionsBuilder<ABPProjectDbContext>()
.UseSqlite(connection)
.Options;
using (var context = new ABPProjectDbContext(options))
{
context.GetService<IRelationalDatabaseCreator>().CreateTables();
}
return connection;
}
}
This was really simple. Just do this:
namespace ABPProject.EntityFrameworkCore;
[DependsOn(
typeof(ABPProjectEntityFrameworkCoreModule),
typeof(ABPProjectTestBaseModule),
typeof(AbpEntityFrameworkCoreSqliteModule)
)]
public class ABPProjectEntityFrameworkCoreTestModule : AbpModule
{
private SqliteConnection _sqliteConnection;
public override void ConfigureServices(ServiceConfigurationContext context)
{
//ConfigureInMemorySqlite(context.Services);
ConfigureLocalDb(context.Services);
}
private void ConfigureInMemorySqlite(IServiceCollection services)
{
_sqliteConnection = CreateDatabaseAndGetConnection_Sqlite();
services.Configure<AbpDbContextOptions>(options =>
{
options.Configure(context =>
{
context.DbContextOptions.UseSqlite(_sqliteConnection);
});
});
}
private void ConfigureLocalDb(IServiceCollection services)
{
services.Configure<AbpDbContextOptions>(options =>
{
options.Configure(context =>
{
context.DbContextOptions.UseSqlServer("Server=(LocalDb)\\MSSQLLocalDB;Database=ABPProject_Unittest;Trusted_Connection=True;TrustServerCertificate=True");
});
});
}
public override void OnApplicationShutdown(ApplicationShutdownContext context)
{
if (_sqliteConnection is not null)
{
_sqliteConnection.Dispose();
}
}
private static SqliteConnection CreateDatabaseAndGetConnection_Sqlite()
{
var connection = new SqliteConnection("Data Source=:memory:");
connection.Open();
var options = new DbContextOptionsBuilder<ABPProjectDbContext>()
.UseSqlite(connection, x => x.UseNetTopologySuite())
.Options;
using (var context = new ABPProjectDbContext(options))
{
context.GetService<IRelationalDatabaseCreator>().CreateTables();
}
return connection;
}
}
Then you need to disable re-seeding each time between test in aspnet-core\test\ABPProject.TestBase\ABPProjectTestBaseModule.cs
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
//SeedTestData(context);
}
How to reset the database when starting all tests? Use XUnit collections, which enable setup before all tests are starting and teardown after all tests are executed.
You can read more about it here https://xunit.net/docs/shared-context but basically, you create a new class (NOTE! You need to place this file in each test project, since the class can not be shared between different test projects):
public class DatabaseFixture : ABPProjectTestBase<ABPProjectEntityFrameworkCoreTestModule>, IAsyncLifetime
{
// ... create your shared vars here
public DatabaseFixture()
{
}
public async Task InitializeAsync()
{
IDbContextProvider<ABPProjectDbContext> _dbContextProvider = GetRequiredService<IDbContextProvider<ABPProjectDbContext>>();
IUnitOfWorkManager _unitOfWorkManager = GetRequiredService<IUnitOfWorkManager>();
IDataSeeder _dataSeeder = GetRequiredService<IDataSeeder>();
using (var uow = _unitOfWorkManager.Begin())
{
var context = await _dbContextProvider.GetDbContextAsync();
var tableNames = context.Model.GetEntityTypes()
.Where(t => t.GetTableName().StartsWith("App"))
.Select(t => t.GetTableName())
.Distinct()
.ToList();
// Disable constraints
foreach (var tableName in tableNames)
{
await context.Database.ExecuteSqlRawAsync($"ALTER TABLE {tableName} NOCHECK CONSTRAINT ALL");
}
// Remove rows
foreach (var tableName in tableNames)
{
await context.Database.ExecuteSqlRawAsync($"DELETE FROM {tableName}");
}
// Re-enable constraints
foreach (var tableName in tableNames)
{
await context.Database.ExecuteSqlRawAsync($"ALTER TABLE {tableName} CHECK CONSTRAINT ALL");
}
}
// Re - seed database
await _dataSeeder.SeedAsync();
}
public async Task DisposeAsync()
{
// ... clean up test data from the database with and use async calls here...
}
}
[CollectionDefinition("Database collection")]
public class DatabaseCollection : ICollectionFixture<DatabaseFixture>
{
// This class has no code, and is never created. Its purpose is simply
// to be the place to apply [CollectionDefinition] and all the
// ICollectionFixture<> interfaces.
}
The last thing is to decorate each test class with following:
namespace ABPProject.Things
{
[Collection("Database collection")]
public class ThingAppService_Tests : ABPProjectApplicationTestBase
{
public readonly DatabaseFixture _fixture;
private readonly IThingAppService _thingAppService;
public ThingAppService_Tests(DatabaseFixture fixture)
{
_fixture = fixture;
_thingAppService = GetRequiredService<IThingAppService>();
}
[Fact]
public async Task GetListAsync_Should_Get_all_Things()
{
// This is a test
}
}
}

Dataseeding not working when using WebApplicationFactory in integration test with nUnit

I'm having problems with seeding my inmemory database with test-data in nUnit integration tests.
I've created a custom WebApplicationFactory so i could use an inmemory database:
public class SiriusWebApplicationFactory : WebApplicationFactory<Startup>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureTestServices(services =>
{
var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(IAuthenticationSchemeProvider));
services.Remove(descriptor);
CleanupDatabaseRegistrations<SiriusContext>(services);
services.AddDbContext<SiriusContext>(options => options.UseInMemoryDatabase($"Testdb-{Guid.NewGuid()}"));
// Build the service provider.
var sp = services.BuildServiceProvider();
// Create a scope to obtain a reference to the database contexts
using var scope = sp.CreateScope();
var scopedServices = scope.ServiceProvider;
var context = scopedServices.GetRequiredService<SiriusContext>();
// Ensure the database is created.
context.Database.EnsureCreated();
});
}
private void CleanupDatabaseRegistrations<TDbContext>(IServiceCollection services) where TDbContext : DbContext
{
var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<TDbContext>));
if (descriptor != null)
{
services.Remove(descriptor);
}
descriptor = services.FirstOrDefault(d => d.ServiceType == typeof(DbContextOptions));
if (descriptor != null)
{
services.Remove(descriptor);
}
descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(TDbContext));
if (descriptor != null)
{
services.Remove(descriptor);
}
}
}
After this, i've created a base class which is being used by all my integration test classes.
This base class implements the nUnit OneTimeSetup method where i create the HttpClient for testing my controllers:
protected HttpClient HttpClient;
protected SiriusContext DbContext;
[OneTimeSetUp]
public void OneTimeSetUp()
{
_webApplicationFactory = new SiriusWebApplicationFactory();
HttpClient = _webApplicationFactory.CreateClient();
DbContext = _webApplicationFactory.Services.GetService<SiriusContext>();
DoReseed();
}
As you can see i'm exposing my DbContext so that my test classes can seed the test data.
The DoReseed method is just an emppty method used by my integration test classes to provide test data.
protected virtual void DoReseed() { }
The implementation inside one of my testclasses looks like this:
public class AdminAanbestedingApplicatieControllerTests : BaseIntegrationTest
{
[Test]
public async Task AanbestedingApplicaties_GetAll_ShouldReturnData()
{
var result = await HttpClient.GetFromApi_AssertSuccess<List<AanbestedingApplicatie>>(RouteConstants.Lijsten.AdminAanbestedingApplicaties.GetAll);
Assert.IsTrue(result.Count > 1);
}
protected override void DoReseed()
{
DbContext.AanbestedingApplicaties.Add(AanbestedingApplicatiesObjectMother.AanbestedingApplicatie_Actief);
DbContext.AanbestedingApplicaties.Add(AanbestedingApplicatiesObjectMother.AanbestedingApplicatie_InActief);
DbContext.AanbestedingApplicaties.Add(AanbestedingApplicatiesObjectMother.AanbestedingApplicatie_ToDelete);
DbContext.AanbestedingApplicaties.Add(AanbestedingApplicatiesObjectMother.AanbestedingApplicatie_ToUpdate);
DbContext.SaveChanges();
}
}
When i execute my test, the seeding gets executed but the repository cannot see the seeded data?
The DbContext inside my repo does not contain my seeded data...
Anyone has any idea why the DbContext inside my repository does not contain the seeded test data?
The repo looks like this:
public IEnumerable<AanbestedingApplicatie> GetAll()
{
return SiriusContext.AanbestedingApplicaties.OrderBy(l => l.Naam).ProjectTo<AanbestedingApplicatie>(Mapper.ConfigurationProvider)
.ToList();
}
The Context has been injected inside the constructor...

Create unit test for View() with a List in ASP.net core MVC application

I am new in unit testing. I have a controller - StudentsController with dependency injection and there my Index() method:
public class StudentsController : Controller
{
public readonly UniversityContext _context;//Database
public StudentsController(UniversityContext context)
{
_context = context;
}//Constructor with database
// GET: Students
public async Task<IActionResult> Index()
{
return View(await _context.Students.ToListAsync());
}
}
Next i need to write a correct unit test code, that check, if:
1) a View() have a list with my students
2) The query with students is not null.
I read about Mock objects, but I don't know how to write the correct code. My code that I wrote so far:
public class StudentsControllerTests
{
[Fact]
public async Task Index_ReturnsAViewResult_WithAListOfStudents()
{
var mockRepo = new Mock<UniversityContext>();
mockRepo.Setup(repo => repo.Students.ToList()).Returns(GetTestStudents());//There i get following error:Expression references a method that does not belong to the mocked object
var controller = new StudentsController(mockRepo.Object);
// Act
var result = controller.Index();
//// Assert
var viewResult = Assert.IsType<ViewResult>(result);
var model = Assert.IsAssignableFrom<IEnumerable<Student>>(
viewResult.ViewData.Model);
Assert.NotNull(model);//Second Condition
}
public List<Student> GetTestStudents()
{
var sessions = new List<Student>();
sessions.Add(new Student()
{
bDate = new DateTime(1994, 7, 2),
Name = "Test One"
});
sessions.Add(new Student()
{
bDate = new DateTime(1995, 7, 1),
Name = "Test Two"
});
return sessions;
}
}
Can someone explain me, how to correct my code?
You only need to mock the members of the context, which in this case is the .Students property. ToList is an extension method being call on the property and cannot be mocked by moq.
Also .Students is a DbSet and would need to be mocked as well.
Using the test classes from this answer :
How to mock an async repository with Entity Framework Core
The following generic extension methods were derived
public static class MockDbSetExtensions {
public static Mock<DbSet<T>> AsDbSetMock<T>(this IEnumerable<T> list) where T : class {
IQueryable<T> queryableList = list.AsQueryable();
Mock<DbSet<T>> dbSetMock = new Mock<DbSet<T>>();
dbSetMock.As<IQueryable<T>>().Setup(x => x.Provider).Returns(queryableList.Provider);
dbSetMock.As<IQueryable<T>>().Setup(x => x.Expression).Returns(queryableList.Expression);
dbSetMock.As<IQueryable<T>>().Setup(x => x.ElementType).Returns(queryableList.ElementType);
dbSetMock.As<IQueryable<T>>().Setup(x => x.GetEnumerator()).Returns(() => queryableList.GetEnumerator());
return dbSetMock;
}
public static Mock<DbSet<T>> ToAsyncDbSetMock<T>(this IEnumerable<T> source)
where T : class {
var data = source.AsQueryable();
var mockSet = new Mock<DbSet<T>>();
mockSet.As<IAsyncEnumerable<T>>()
.Setup(m => m.GetEnumerator())
.Returns(new TestAsyncEnumerator<T>(data.GetEnumerator()));
mockSet.As<IQueryable<T>>()
.Setup(m => m.Provider)
.Returns(new TestAsyncQueryProvider<T>(data.Provider));
mockSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(data.Expression);
mockSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(data.ElementType);
mockSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(() => data.GetEnumerator());
return mockSet;
}
}
With the above utilities, update
mockRepo.Setup(repo => repo.Students.ToList()).Returns(GetTestStudents());
To
var studentsMockedDbSet = GetTestStudents().ToAsyncDbSetMock();
mockRepo.Setup(repo => repo.Students).Returns(studentsMockedDbSet.Object);

Entity Framework 6 dbContext not saving changes

In our MVC 5 project our database context is instantiate in the AccountController like this
private CustomersContext _customersContext;
public CustomersContext CustContext
{
get
{
return _customersContext ?? new CustomersContext();
}
private set
{
_customersContext = value;
}
}
Each customer is referred by a number of sources. The routine below changes the UserId of the referral source to a new user.
var referralList = CustContext.Referrals.Where(d => d.UserId == membershipUser.Id);
foreach (Referral referral in referralList)
{
referral.UserId = newUser.Id;
}
Stepping trough the code I can see referral.UserId being updated. However
var result = await CustContext.SaveChangesAsync();
returns 0. The database is not updated.
CustomersContext looks like this
{
public partial class CustomersContext : IdentityDbContext<ApplicationUser>//, ICustomersContext
{
public CustomersContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
public static CustomersContext Create()
{
return new CustomersContext();
}
public virtual DbSet<ReferralSource> ReferralSources { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ApplicationUser>()
.HasMany(e => e.Referrals)
.WithRequired(e => e.User)
.HasForeignKey(e => e.UserId)
.WillCascadeOnDelete(false);
I don't see any sql emitted in SQL Profiler. Why doesn't the database context save changes?
Before calling var result = await CustContext.SaveChangesAsync(); you need to set the state of the entities that you want to be modified. Somthing like:
var referralList = CustContext.Referrals.Where(d => d.UserId == membershipUser.Id);
foreach (Referral referral in referralList)
{
referral.UserId = newUser.Id;
CustContext.Entry(referral).State = System.Data.Entity.EntityState.Modified;
}
var result = await CustContext.SaveChangesAsync();
The answer provided by #Issac did not solve my problem, but it did put me on the road to a solution. The error
An entity object cannot be referenced by multiple instances of IEntityChangeTracker.
suggested there were multiple instances of dbContext. I removed the context from the CTOR and instantiated the context within a using statement
using (CustomersContext customersContext = new CustomersContext())
{
var referralList = customersContext.Referrals.Where(d => d.UserId == membershipUser.Id);
foreach (Referral referral in referralList)
{
referral.UserId = newUser.Id;
}
var result = await customersContext.SaveChangesAsync();
}
and now all is tickety-boo

Testing code in a custom NancyFx Bootstrapper

I have a custom Nancy Bootstrapper which uses StructureMapNancyBootstrapper but the issue is the same regardless of container.
public class CustomNancyBootstrapper : StructureMapNancyBootstrapper
{
protected override void RequestStartup(IContainer container, IPipelines pipelines, NancyContext context)
{
var auth = container.GetInstance<ICustomAuth>();
auth.Authenticate(context);
}
}
I want to write a test to assert that Authenticate is called with the context... something like this...
[Test]
public void RequestStartup_Calls_CustomAuth_Authenticate_WithContext()
{
// set up
var mockAuthentication = new Mock<ICustomAuth>();
var mockContainer = new Mock<IContainer>();
var mockPipelines = new Mock<IPipelines>();
var context = new NancyContext();
mockContainer.Setup(x => x.GetInstance<ICustomAuth>()).Returns(mockAuthentication.Object);
// exercise
_bootstrapper.RequestStartup(_mockContainer.Object, _mockPipelines.Object, context);
// verify
mockAuthentication.Verify(x => x.Authenticate(context), Times.Once);
}
The problem is that I can't call RequestStartup because it's protected as defined in NancyBootstrapperBase.
protected virtual void RequestStartup(TContainer container, IPipelines pipelines, NancyContext context);
Is there a "proper"/"offical" Nancy way to do this without creating another derived class and exposing the methods as that just seems like a hack?
Thanks
I guess you can "fake" the request by using Browser from Nancy.Testing:
var browser = new Browser(new CustomNancyBootstrapper());
var response = browser.Get("/whatever");
There is a good set of articles about testing NancyFx application:
http://www.marcusoft.net/2013/01/NancyTesting1.html
Turns out Nancy offers a IRequetStartup interface so you can take the code out of the custom bootstrapper and do something like this...
public class MyRequestStart : IRequestStartup
{
private readonly ICustomAuth _customAuthentication;
public MyRequestStart(ICustomAuth customAuthentication)
{
if (customAuthentication == null)
{
throw new ArgumentNullException(nameof(customAuthentication));
}
_customAuthentication = customAuthentication;
}
public void Initialize(IPipelines pipelines, NancyContext context)
{
_customAuthentication.Authenticate(context);
}
}
and the test is easy and concise
[Test]
public void When_Initialize_Calls_CustomAuth_Authenticate_WithContext()
{
// set up
var mockAuth = new Mock<ICustomAuth>();
var requestStartup = new MyRequestStart(mockAuth.Object);
var mockPipeline = new Mock<IPipelines>();
var context = new NancyContext();
// exercise
requestStartup.Initialize(mockPipeline.Object, context);
// verify
mockAuth.Verify(x => x.Authenticate(context), Times.Once);
}
https://github.com/NancyFx/Nancy/wiki/The-Application-Before%2C-After-and-OnError-pipelines#implementing-interfaces