Testing IDbCommandInterceptor with InMemoryDatabase for EFCore - unit-testing

I have written an IDbCommandInterceptor to intercept calls to EntityFrameworkCore DbContext. Now I would like to write unit tests for the interceptor. I am able to intercept and run custom logic for an actual db (tested with SqlServer and MySql), however when testing with an InMemoryDatabase (provided by .Net for testing purposes), my interceptor methods are never called.
Here's how I'm setting up my unit test:
TestDbContext class:
public class TestDbContext : DbContext
{
public TestDbContext(DbContextOptions options) : base(options) { }
public DbSet<User> Users { get; set; }
}
public class User
{
public int UserId { get; set; }
}
This is how I'm initializing the context in my test:
private TestDbContext GetTestDbContext()
{
var options = new DbContextOptionsBuilder<TestDbContext>()
.UseInMemoryDatabase(databaseName: "TestDB")
.AddInterceptors(new MyInterceptor())
.Options;
var context = new TestDbContext(options);
// Seed data
context.Users.Add(new User { UserId = 1 });
context.Users.Add(new User { UserId = 2 });
context.Users.Add(new User { UserId = 3 });
context.SaveChanges();
return context;
}
Now, when I call SaveChanges() or do a query on this context, I expect it to invoke my MyInterceptor's overridden ReaderExecuted() method the same way it does when I use an actual db.
Am I missing something? Or DbCommandInterceptor does not work with InMemoryDatabase?

The InMemory provider is not a relational provider, and therefore does not implement DbCommand interceptors. You can use a SQL Server / SQL Server LocalDb / SQlite database for this kind of testing.

Related

Mock DbQuery to use FromSql from unit tests

I am using EF Core in a projet to get stored procedure calling. In my context i have the following :
public class MyContext : DbContext
{
public DbQuery<User> UserQuery { get; set; }
public MyContext(DbContextOptions<MyContext> options) : base(options) { }
}
And i call the stored procedure like this :
public virtual async Task<User> GetUserAsync(string name)
{
return await MyContext.Query<User>()
.FromSql($"EXEC [dbo].[GetUser], #Login = {name}")
.FirstOrDefaultAsync();
}
Code is working fine. I need to test this method in unit tests, i'm using InMemoryDatabase to mock my context MyContext like this :
[Fact]
public async Task GetUserAsync_should_return_first_user_with_login_and_password_if_exists()
{
// Arrange
var users = new List<User>
{
new User()
{
Login = "test#outlook.fr",
Password = "pass1",
},
};
var options = new DbContextOptionsBuilder<MyContext>()
.UseInMemoryDatabase(databaseName: "BddName")
.Options;
var context = new MyContext(options);
var loginProvider = A.Fake<LoginProvider>(opts => opts.WithArgumentsForConstructor(() => new LoginProvider(context)));
// Act
// Assert
context.Dispose();
}
And i have no idea how can i set my list into the result of the stored procedure called from DbQuery. I tried to follow this article : https://nodogmablog.bryanhogan.net/2017/11/unit-testing-entity-framework-core-stored-procedures/ but it works for DbSet only and not DbQuery.
I need some advices for this case.
Thanks in advance.
The link in the OP does apply to the DbQuery type as well, as you're mocking the provider. Both DbSet and DbQuery work in the same way in this regard.
See https://stackoverflow.com/a/56940311/2975810 for a previous answer on the topic.

How do I get my NancyFX unit tests to run my validation?

I'm using NancyFX with FluentValidation, as documented at https://github.com/NancyFx/Nancy/wiki/Nancy-and-Validation. My web app is running fine and validation is working perfectly, but when I try to unit test any of the modules that use validation, I'm getting an error
Nancy.Validation.ModelValidationException : No model validator factory could be located.
Please ensure that you have an appropriate validation package installed, such as
one of the Nancy.Validation packages.
I've verified that my unit test project has references to the Nancy.Validation.FluentValidation and FluentValidation assemblies.
My test code looks like this:
public class ArticleModuleTests {
private Browser browser;
private IDatabase db;
const int USER_ID = 123;
const int ARTICLE_ID = 456;
[SetUp]
public void SetUp() {
var user = new User { Username = "test", Id = USER_ID };
db = A.Fake<IDatabase>();
browser = new Browser(with => {
with.Module<ArticleModule>();
with.RequestStartup((container, pipelines, context) => context.CurrentUser = user);
with.Dependency(db);
});
}
[Test]
public void User_Can_Publish_Article() {
var article = new { title = "Test" };
var result = browser.Post($"/users/{USER_ID}/articles", with => {
with.HttpRequest();
with.Body(JsonConvert.SerializeObject(article));
});
result.StatusCode.ShouldBe(HttpStatusCode.BadRequest);
}
}
My module code is:
public class ArticlesModule : NancyModule {
private IDatabase database;
public ArticlesModule(IDatabase db) {
this.database = db;
Post["/users/{id:int}/articles"] = args => PostArticle(args.id);
}
private dynamic PostArticle(int userId) {
var article = this.Bind<Article>();
var validation = this.Validate(article);
if (!validation.IsValid) return Negotiate.WithModel(validation).WithStatusCode(HttpStatusCode.BadRequest);
database.CreateArticle(userId, article);
return NegotiatorExtensions.WithModel(Negotiate, result)
.WithStatusCode(HttpStatusCode.Created)
.WithHeader("Location", $"http://whatever/users/{userId}/articles/{article.Id}");
}
}
and my validation class is:
public class ArticleValidator : AbstractValidator<Article> {
public ArticleValidator() {
RuleFor(article => article.Title)
.NotEmpty()
.WithMessage("The \"title\" property is required");
RuleFor(article => article.Title)
.Length(2, 50)
.WithMessage("The \"title\" property must be between 2 and 50 characters");
}
}
The NancyFX docs say "Create a validation class... There is no need to register it anywhere as it is automatically detected." - but I'm guessing whatever automatic detection is wired up isn't firing for a unit test project. I'm building on .NET 4.5.2 and using NCrunch as my test runner; what do I need to do to get my test code to pick up the same validation classes as my application modules?
OK, turns out that because NancyFX detects and instantiates validation classes automatically, there's no explicit references in my code to Nancy.Validation.FluentValidation, and so NCrunch is omitting this assembly when building my test project. Setting "Copy referenced assemblies to workspace" in the NCrunch project settings fixed it.

following constructor parameters did not have matching fixture data : DbContextOptions'1options [duplicate]

I have build a WebAPI and apart from my tests running on Postman I would like to implement some Integration/Unit tests.
Now my business logic is very thin, most of the time its more of CRUD actions, therefore I wanted to start with testing my Controllers.
I have a basic setup. Repository pattern (interfaces), Services (business logic) and Controllers.
The flow goes Controller (DI Service) -> Service (DI Repo) -> Repo Action!
So what I did was override my Startup file to change into a in memory database and the rest should be fine (I would assume) Services are added, repos are added and now I am pointing into a in memory DB which is fine for my basic testing.
namespace API.UnitTests
{
public class TestStartup : Startup
{
public TestStartup(IHostingEnvironment env)
: base(env)
{
}
public void ConfigureTestServices(IServiceCollection services)
{
base.ConfigureServices(services);
//services.Replace<IService, IMockedService>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
base.Configure(app, env, loggerFactory);
}
public override void SetUpDataBase(IServiceCollection services)
{
var connectionStringBuilder = new SqliteConnectionStringBuilder { DataSource = ":memory:" };
var connectionString = connectionStringBuilder.ToString();
var connection = new SqliteConnection(connectionString);
services
.AddEntityFrameworkSqlite()
.AddDbContext<ApplicationDbContext>(
options => options.UseSqlite(connection)
);
}
}
}
I wrote my first test, but the DatasourceService is not there:
The following constructor parameters did not have matching fixture data: DatasourceService datasourceService
namespace API.UnitTests
{
public class DatasourceControllerTest
{
private readonly DatasourceService _datasourceService;
public DatasourceControllerTest(DatasourceService datasourceService)
{
_datasourceService = datasourceService;
}
[Xunit.Theory,
InlineData(1)]
public void GetAll(int companyFk) {
Assert.NotEmpty(_datasourceService.GetAll(companyFk));
}
}
}
What am I missing?
You can't use dependency injection on test classes. You can only let xunit inject special fixtures via constructor (see docs).
For Integration Testing you want to use the TestServer class from Microsoft.AspNetCore.TestHost package and a separate Startup.cs class (easier to setup configuration than inheritance imho).
public class TestStartup : Startup
{
public TestStartup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
public void ConfigureTestServices(IServiceCollection services)
{
services.Replace(ServiceDescriptor.Scoped<IService, MockedService>());
services.AddEntityFrameworkSqlite()
.AddDbContext<ApplicationDbContext>(
options => options.UseSqlite(connection)
);
}
public void Configure(IApplicationBuilder app)
{
// your usual registrations there
}
}
In your unit test project, you need to create an instance of the TestServer and perform the test.
public class DatasourceControllerTest
{
private readonly TestServer _server;
private readonly HttpClient _client;
public DatasourceControllerTest()
{
// Arrange
_server = new TestServer(new WebHostBuilder()
.UseStartup<TestStartup>());
_client = _server.CreateClient();
}
[Xunit.Theory,
InlineData(1)]
public async Task GetAll(int companyFk) {
// Act
var response = await _client.GetAsync($"/api/datasource/{companyFk}");
// expected result from rest service
var expected = #"[{""data"":""value1"", ""data2"":""value2""}]";
// Assert
// This makes sure, you return a success http code back in case of 4xx status codes
// or exceptions (5xx codes) it throws an exception
response.EnsureSuccessStatusCode();
var resultString = await response.Content.ReadAsStringAsync();
Assert.Equals(resultString, expectedString);
}
}
Now, when you call operations which write to the database, you can also check if the data is really written to the database:
[Xunit.Theory,
InlineData(1)]
public async Task GetAll(int companyFk) {
// Act
var response = await _client.DeleteAsync($"/api/datasource/{companyFk}");
// expected result from rest service
// Assert
response.EnsureSuccessStatusCode();
// now check if its really gone in the database. For this you need an instance
// of the in memory Sqlite DB. TestServer has a property Host, which is an IWebHost
// and it has a property Services which is the IoC container
var provider = _server.Host.Services;
var dbContext = provider.GetRequiredService<ApplicationDbContext>();
var result = await dbContext.YourTable.Where(entity => entity.Id == companyFk).Any();
// if it was deleted, the query should result in false
Assert.False(result);
}
Now you can use Xunit.DependencyInjection in your tests.
namespace Your.Test.Project
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IDependency, DependencyClass>();
}
}
}
your DI-classes:
public interface IDependency
{
int Value { get; }
}
internal class DependencyClass : IDependency
{
public int Value => 1;
}
and XUnit-test:
public class MyAwesomeTests
{
private readonly IDependency _d;
public MyAwesomeTests(IDependency d) => _d = d;
[Fact]
public void AssertThatWeDoStuff()
{
Assert.Equal(1, _d.Value);
}
}

Unit testing when Database is involved

I'm quite new to unit testing and I need an hand to understand if I'm doing things in the correct way. My major problem is regarding the DB testing... Here's my code then I'll expose my perplexities
Consider this class that's an item of a pipeline I've to perform
public class RetrieveApplicationUsernamePipelineStep : IPipelineStep
{
public const string RetrieveApplicationUsernameKey = "RetrieveApplicationUsername";
private readonly IRetrieveApplicationUserRepository repository;
public int Order => 3;
public string Name => RetrieveApplicationUsernameKey;
public RetrieveApplicationUsernamePipelineStep(IRetrieveApplicationUserRepository repository)
{
this.repository = repository;
}
public async Task<IDictionary<string, object>> Action(IDictionary<string, object> context)
{
string res = await repository.GetApplicationUser(context);
context[Resources.ApplicationUser] = res;
return context;
}
}
I wrote the following tests
[TestFixture]
public class RetrieveApplicationUsernamePipelineStepTests
{
private IRetrieveApplicationUserRepository retrieveApplicationUserRepository;
[OneTimeSetUp]
public void Start()
{
var configuration = new ConfigurationFromConfigFile();
retrieveApplicationUserRepository = new RetrieveApplicationUserRepository(configuration);
}
[Test]
public async Task ActionSuccessfullyCompleted()
{
var context = new Dictionary<string, object>();
var repository = Substitute.For<IRetrieveApplicationUserRepository>();
repository.GetApplicationUser(context).Returns("user1");
var pipeline = new RetrieveApplicationUsernamePipelineStep(repository);
var res = await pipeline.Action(context);
Assert.IsNotNull(res[Resources.ApplicationUser]);
Assert.IsNotEmpty((string)res[Resources.ApplicationUser]);
}
[Test]
public void ActionFailingCompleted()
{
var context = new Dictionary<string, object>();
var repository = Substitute.For<IRetrieveApplicationUserRepository>();
repository.GetApplicationUser(context).Throws(new UserMappingNotFoundException());
var pipeline = new RetrieveApplicationUsernamePipelineStep(repository);
Assert.ThrowsAsync<UserMappingNotFoundException>(async () => await pipeline.Action(context));
}
[Test]
public void NameTest()
{
var pipeline = new RetrieveApplicationUsernamePipelineStep(retrieveApplicationUserRepository);
Assert.IsTrue(pipeline.Name == RetrieveApplicationUsernamePipelineStep.RetrieveApplicationUsernameKey);
}
[Test]
public void OrderTest()
{
var pipeline = new RetrieveApplicationUsernamePipelineStep(retrieveApplicationUserRepository);
Assert.IsTrue(pipeline.Order == 3);
}
}
And those test works fine since for ActionSuccessfullyCompleted and ActionFailingCompleted I substitute the IRetrieveApplicationUserRepository's result with my expected one.
The real implementation of ther repository is
public class RetrieveApplicationUserRepository : IRetrieveApplicationUserRepository
{
#region Variables
private readonly IConfiguration configuration;
#endregion
#region Ctor
public RetrieveApplicationUserRepository(IConfiguration configuration)
{
this.configuration = configuration;
}
#endregion
#region IRetrieveApplicationUserRepository
public async Task<string> GetApplicationUser(IDictionary<string, object> context)
{
if (configuration.AppSettings[Resources.ApplicationUserFromDomainUserKey] == null)
throw new KeyNotFoundException(Resources.ApplicationUserFromDomainUserKey);
if (string.IsNullOrEmpty(configuration.ConnectionString))
throw new NullReferenceException();
string storedProcedure = configuration.AppSettings.Get(Resources.ApplicationUserFromDomainUserKey);
string result;
using (var sqlConnection = new SqlConnection(configuration.ConnectionString))
{
using (var sqlCommand = new SqlCommand(storedProcedure, sqlConnection))
{
sqlCommand.CommandType = CommandType.StoredProcedure;
sqlCommand.Parameters.AddWithValue("#DOMAINUSER", context[Resources.DomainUser]);
sqlCommand.Parameters.AddWithValue("#DOMAIN", context[Resources.DomainName]);
sqlCommand.Parameters.AddWithValue("#APPID", context[Resources.ApplicationId]);
sqlConnection.Open();
result = (string)await sqlCommand.ExecuteScalarAsync();
}
}
if (result == null)
throw new UserMappingNotFoundException();
return result;
}
#endregion
}
Here're the questions :
Are the test I wrote correct?
I've seen using Resharper's Code Coverage that it wants me to test-cover the properties...is there a way I can avoid this? is this test meaningful?
What's your approach when you've to unit test component that're related to DB? Have you got a real-db that's used for test? Consider that the real DB is about 10Gb so I don't want to have a copy as mdf (condider I can have this) just to test a small portion of the DB
Talking with my colleagues they told me to use test just for TDD while I wish to use them to avoid regressions
Going back to the DB question, I don't want to have a test where I write if username is "John" and maybe tomorrow John user won't be present in the DB anymore,so that the test I expect to pass wont' pass anoymore
The usual approach is to isolate the database side with an abstraction, so you can provide a test dummy (mock, fake, etc) of that abstraction. Only test an actual database when you do the integration testing.
For the tests of database stored procedures, you may well want a different test harness, creating a new test database in memory (equivalently, in a RAM-backed filesystem). You only need to populate enough data for the individual test (we're doing functional testing here, not performance testing), and you may be able to retain table structure across tests with judicious use of rollback.
I have done this, but it's some time ago, so I'll refrain from giving examples that may be no longer state-of-the-art (even if the code does still exist, and if I could find it).

How to create a test for Fluent nHibernate conventions?

I am creating a set of conventions for Fluent nHibernate. I create a few conventions, like primary key, foreign key and many-to-many table.
I would like to be able to test out these conventions with an in memory database to see if I coded these conventions correctly.
Currently, I am setting up nHibernate using an SQlite in-memory database like this:
Configuration configuration = null;
var factory = Fluently.Configure()
.Database(SQLiteConfiguration.Standard.InMemory().ShowSql())
.Mappings(m =>
{
m.FluentMappings.AddFromAssemblyOf<DataLayer>();
m.FluentMappings.Conventions.AddFromAssemblyOf<DataLayer>();
})
.ExposeConfiguration((c) => configuration = c)
.BuildSessionFactory();
var session = factory.OpenSession();
var export = new SchemaExport(configuration);
export.Execute(true, true, false, session.Connection, null);
I create a test case, but I don't know test a naming convention. How can I achieve some unit tests on a conventions using a Visual Studio test project?
What I did is not testing against the database, but just test the generated mapping. So for example I have a convention that says that all foreign keys are written like ID and I test it like (I use xunit and not mstest, but hopefully you can get the concept...):
[Fact]
public void AddDefaultConventions_ShouldNameMappingToForeinKeyCorrectly()
{
var configuration = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008)
.Mappings(m =>
{
m.FluentMappings.Conventions.Add(new CustomForeignKeyConvention());
m.FluentMappings.Add<TestClassMap>();
m.FluentMappings.Add<TestClass2Map>();
})
.BuildConfiguration();
var typeMapping = configuration.GetClassMapping(typeof(TestClass2));
var property = typeMapping.GetProperty("Parent");
Assert.Equal("ParentID", property.ColumnIterator.First().Text);
}
private class TestClass
{
public virtual int ID { get; set; }
}
private class TestClass2
{
public virtual int ID { get; set; }
public virtual TestClass Parent { get; set; }
}
private class TestClassMap : ClassMap<TestClass>
{
public TestClassMap()
{
Id(x => x.ID);
}
}
private class TestClass2Map : ClassMap<TestClass2>
{
public TestClass2Map()
{
Id(x => x.ID);
References(x => x.Parent);
}
}
Btw. it wouldn't be too difficult to test against a DB, just try to select something from TestClass2 and make sure no exception is thrown... but I think the way I showed is easier and I trust that when FluentNhibernate can generate the correct NHibernate mapping, NHibernate can generate the correct queries for me.