How to prevent automatic inclusion of navigation properties in unit tests - c#

I am currently developing an app store style API which has the following entities (plus many others, but not relevant to the problem):
App (1 to many relationship to AppRevision - contains IEnumerable property)
AppRevision
Installation
I have come across an odd problem where the behaviour of EF differs in unit tests to when actually running the API, in that navigation properties are automatically being included when unit testing.
Take the following code snippet from my command handler:
App app = await this.context.Apps
.Include(a => a.Installations)
.FirstOrDefaultAsync(a => a.Id == command.AppId);
if (app != null) {
// Code omitted for brevity
}
When running the API, if I inspect app after this code has been run, the AppRevisions collection on the App entity is empty, as you would expect as I have not expliclity told EF to .Include(a => a.AppRevisions) - the API then throws an exception when trying to process code later on that needs this data to be there.
Now look at the following unit test for the same handler:
[Fact]
public async void Handle_ShouldAddInstallationRecord_WhenDataIsValid()
{
Guid testGuid = Guid.NewGuid();
CreateInstallationCommand command = new CreateInstallationCommand(testGuid, "ABC", "abc#abc.com", null);
using (TestContext context = new TestContextFactory().CreateTestContext())
{
context.Apps.Add(new App() { Id = testGuid });
context.AppRevisions.Add(new AppRevision() { Id = Guid.NewGuid(), AppId = testGuid, Status = AppRevisionStatus.Approved, IsListed = true });
await context.SaveChangesAsync();
CreateInstallationCommandHandler handler = new CreateInstallationCommandHandler(context);
CommandResult result = await handler.Handle(command, new CancellationToken());
Assert.True(result);
Assert.Single(context.Installations);
}
}
If I step through this test, when I get to the handler and inspect the app variable, the AppRevisions collection has automatically been populated. As a result, the test passes because the code that requires the AppRevisions collection to be populated can execute.
The expectation is that this test should actually fail, because I'm not telling EF to include those entities in the query.
I am using a Sqlite in memory database to create the database context for my unit tests and running .NET Core 2.2
I originally thought this was something to do with the changetracker. While disabling this does solve the immediate problem reported above, it creates a load of other problems so isn't a viable solution (and probably wouldn't be the correct one anyway)
Any suggestions gratefully received

For anyone who comes across this post in the future, the solution is as per the comments on the original question, to use separate contexts for seeding test data and getting the data later in the test:
[Fact]
public async void Handle_ShouldAddInstallationRecord_WhenDataIsValid()
{
Guid testGuid = Guid.NewGuid();
CreateInstallationCommand command = new CreateInstallationCommand(testGuid, "ABC", "abc#abc.com", null);
using (TestContextFactory contextFactory = new TestContextFactory())
{
using (TestContext seedContext = contextFactory.CreateTestContext())
{
seedContext.Apps.Add(new App() { Id = testGuid });
seedContext.AppRevisions.Add(new AppRevision() { Id = Guid.NewGuid(), AppId = testGuid, Status = AppRevisionStatus.Approved, IsListed = true });
await seedContext.SaveChangesAsync();
}
using (TestContext getContext = contextFactory.CreateTestContext())
{
CreateInstallationCommandHandler handler = new CreateInstallationCommandHandler(getContext);
CommandResult result = await handler.Handle(command, new CancellationToken());
Assert.True(result);
Assert.Single(getContext.Installations);
}
}
}

Related

DbContext Mocking ChangeTracker with xUnit - Entity Framework

I am currently trying to track changes done to the entities. The application is an ASP.NET MVC app using Entity Framework. Within the repository I use a basic command for saving
DbContext.SaveChangesAsync()
However, before that I would like to check what have changed in the entity and thus I use
DbContext.ChangeTracker.Entries<TEntity>();
All works fine when I am running the solution, but starting up the xUnit unit tests returns an error
System.InvalidOperationException : No connection string named "XXXX" could be found in the application config file
It seems like the mocking that I do doesn't mock the ChangeTracker - how can I achieve it so that I can mock the change tracker? Is there maybe other option how to prevent such a problem? How do you normally mock the DbContext so that it covers the change tracker as well?
Example of the code:
public virtual async Task UpdateAsync(params TEntity[] items)
{
foreach (var item in items.Where(i => i != null).ToList())
{
if (item.Id == 0)
{
throw new CustomException(CustomExceptionReason.CannotUpdateNotCreated, item.Id.ToString());
}
DbContext.ChangeTracker.DetectChanges();
var otherCHanges = DbContext.ChangeTracker.Entries<TEntity>();
UpdateModifiedDate(item);
}
await this.DbContext.SaveChangesAsync();
foreach (var item in items)
{
CacheRemove(item);
}
}
Example of mocking:
var users = new[] { new User {Id = 1, UserName = "TestUserName", FirstName = "Test", LastName = "User"} };
var dbContextMock = new DbContextMock<MyEntities>();
dbContextMock.CreateDbSetMock(x => x.Users, (e, c) => e.Id, users);
Thank you!

How do I write unit tests for ASP.NET Core controllers that actually use my Database Context?

There seems to be little information about how to write good unit tests for actual ASP.NET Core controller actions. Any guidance about how to make this work for real?
I've got a system that seems to be working pretty well right now, so I thought I'd share it and see if it doesn't help someone else out. There's a really useful article in the Entity Framework documentation that points the way. But here's how I incorporated it into an actual working application.
1. Create an ASP.NET Core Web App in your solution
There are tons of great articles out there to help you get started. The documentation for basic setup and scaffolding is very helpful. For this purpose, you'll want to create a web app with Individual User Accounts so that your ApplicationDbContext is setup to work with EntityFramework automatically.
1a. Scaffold a controller
Use the information included in the documentation to create a simple controller with basic CRUD actions.
2. Create a separate class library for your unit tests
In your solution, create a new .NET Core Library and reference your newly created web app. In my example, the model I'm using is called Company, and it uses the CompaniesController.
2a. Add the necessary packages to your test library
For this project, I use xUnit as my test runner, Moq for mocking objects, and FluentAssertions to make more meaningful assertions. Add those three libraries to your project using NuGet Package Manager and/or Console. You may need to search for them with the Show Prerelease checkbox selected.
You will also need a couple of packages to use EntityFramework's new Sqlite-InMemory database option. This is the secret sauce. Below are a list of the package names on NuGet:
Microsoft.Data.Sqlite
Microsoft.EntityFrameworkCore.InMemory [emphasis added]
Microsoft.EntityFrameworkCore.Sqlite [emphasis added]
3. Setup Your Test Fixture
Per the article I mentioned earlier, there is a simple, beautiful way to set up Sqlite to work as an in-memory, relational database which you can run your tests against.
You'll want to write your unit test methods so that each method has a new, clean copy of the database. The article above shows you how to do that on a one-off basis. Here's how I set up my fixture to be as DRY as possible.
3a. Synchronous Controller Actions
I've written the following method that allows me to write tests using the Arrange/Act/Assert model, with each stage acting as a parameter in my test. Below is the code for the method and the relevant class properties in the TestFixture that it references, and finally an example of what it looks like to call the code.
public class TestFixture {
public SqliteConnection ConnectionFactory() => new SqliteConnection("DataSource=:memory:");
public DbContextOptions<ApplicationDbContext> DbOptionsFactory(SqliteConnection connection) =>
new DbContextOptionsBuilder<ApplicationDbContext>()
.UseSqlite(connection)
.Options;
public Company CompanyFactory() => new Company {Name = Guid.NewGuid().ToString()};
public void RunWithDatabase(
Action<ApplicationDbContext> arrange,
Func<ApplicationDbContext, IActionResult> act,
Action<IActionResult> assert)
{
var connection = ConnectionFactory();
connection.Open();
try
{
var options = DbOptionsFactory(connection);
using (var context = new ApplicationDbContext(options))
{
context.Database.EnsureCreated();
// Arrange
arrange?.Invoke(context);
}
using (var context = new ApplicationDbContext(options))
{
// Act (and pass result into assert)
var result = act.Invoke(context);
// Assert
assert.Invoke(result);
}
}
finally
{
connection.Close();
}
}
...
}
Here's what it looks like to call the code to test the Create method on the CompaniesController (I use parameter names to help me keep my expressions straight, but you don't strictly need them):
[Fact]
public void Get_ReturnsAViewResult()
{
_fixture.RunWithDatabase(
arrange: null,
act: context => new CompaniesController(context, _logger).Create(),
assert: result => result.Should().BeOfType<ViewResult>()
);
}
My CompaniesController class requires a logger, that I mock up with Moq and store as a variable in my TestFixture.
3b. Asynchronous Controller Actions
Of course, many of the built-in ASP.NET Core actions are asynchronous. To use this structure with those, I've written the method below:
public class TestFixture {
...
public async Task RunWithDatabaseAsync(
Func<ApplicationDbContext, Task> arrange,
Func<ApplicationDbContext, Task<IActionResult>> act,
Action<IActionResult> assert)
{
var connection = ConnectionFactory();
await connection.OpenAsync();
try
{
var options = DbOptionsFactory(connection);
using (var context = new ApplicationDbContext(options))
{
await context.Database.EnsureCreatedAsync();
if (arrange != null) await arrange.Invoke(context);
}
using (var context = new ApplicationDbContext(options))
{
var result = await act.Invoke(context);
assert.Invoke(result);
}
}
finally
{
connection.Close();
}
}
}
It's almost exactly the same, just setup with async methods and awaiters. Below, an example of calling these methods:
[Fact]
public async Task Post_WhenViewModelDoesNotMatchId_ReturnsNotFound()
{
await _fixture.RunWithDatabaseAsync(
arrange: async context =>
{
context.Company.Add(CompanyFactory());
await context.SaveChangesAsync();
},
act: async context => await new CompaniesController(context, _logger).Edit(1, CompanyFactory()),
assert: result => result.Should().BeOfType<NotFoundResult>()
);
}
3c. Async Actions with Data
Of course, sometimes you'll have to pass data back-and-forth between the stages of testing. Here's a method I wrote that allows you to do that:
public class TestFixture {
...
public async Task RunWithDatabaseAsync(
Func<ApplicationDbContext, Task<dynamic>> arrange,
Func<ApplicationDbContext, dynamic, Task<IActionResult>> act,
Action<IActionResult, dynamic> assert)
{
var connection = ConnectionFactory();
await connection.OpenAsync();
try
{
object data;
var options = DbOptionsFactory(connection);
using (var context = new ApplicationDbContext(options))
{
await context.Database.EnsureCreatedAsync();
data = arrange != null
? await arrange?.Invoke(context)
: null;
}
using (var context = new ApplicationDbContext(options))
{
var result = await act.Invoke(context, data);
assert.Invoke(result, data);
}
}
finally
{
connection.Close();
}
}
}
And, of course, an example of how I use this code:
[Fact]
public async Task Post_WithInvalidModel_ReturnsModelErrors()
{
await _fixture.RunWithDatabaseAsync(
arrange: async context =>
{
var data = new
{
Key = "Name",
Message = "Name cannot be null",
Company = CompanyFactory()
};
context.Company.Add(data.Company);
await context.SaveChangesAsync();
return data;
},
act: async (context, data) =>
{
var ctrl = new CompaniesController(context, _logger);
ctrl.ModelState.AddModelError(data.Key, data.Message);
return await ctrl.Edit(1, data.Company);
},
assert: (result, data) => result.As<ViewResult>()
.ViewData.ModelState.Keys.Should().Contain((string) data.Key)
);
}
Conclusion
I really hope this helps somebody getting on their feet with C# and the awesome new stuff in ASP.NET Core. If you have any questions, criticisms, or suggestions, please let me know! I'm still new at this, too, so any constructive feedback is invaluable to me!

Properly handling DbContexts in ASP.NET Core WebApi

Got a small confusion here.
I'm not sure if I am handling my DbContext throughout the WebApi properly.
I do have some controllers that do some operations on my DB (Inserts/Updates with EF) and after doing these actions I do trigger an event.
In my EventArgs (I have a custom class which inherits from EventArgs) I pass my DbContext and I use it in the event handler to log these operations (basically I just log authenticated user API requests).
In the event handler when I am trying to commit my changes (await SaveChangesAsync) I get an error : "Using a disposed object...etc" basically noticing me that at the first time I use await in my async void (fire and forget) I notify the caller to dispose the Dbcontext object.
Not using async works and the only workaround that I've mangaged to put out is by creating another instance of DbContext by getting the SQLConnectionString of the EventArgs passed DbContext.
Before posting I did made a small research based on my issue
Entity Framework disposing with async controllers in Web api/MVC
This is how I pass parameters to my OnRequestCompletedEvent
OnRequestCompleted(dbContext: dbContext,requestJson: JsonConvert.SerializeObject);
This is the OnRequestCompleted() declaration
protected virtual void OnRequestCompleted(int typeOfQuery,PartnerFiscalNumberContext dbContext,string requestJson,string appId)
{
RequestCompleted?.Invoke(this,new MiningResultEventArgs()
{
TypeOfQuery = typeOfQuery,
DbContext = dbContext,
RequestJson = requestJson,
AppId = appId
});
}
And this is how I process and use my dbContext
var appId = miningResultEventArgs.AppId;
var requestJson = miningResultEventArgs.RequestJson;
var typeOfQuery = miningResultEventArgs.TypeOfQuery;
var requestType = miningResultEventArgs.DbContext.RequestType.FirstAsync(x => x.Id == typeOfQuery).Result;
var apiUserRequester = miningResultEventArgs.DbContext.ApiUsers.FirstAsync(x => x.AppId == appId).Result;
var apiRequest = new ApiUserRequest()
{
ApiUser = apiUserRequester,
RequestJson = requestJson,
RequestType = requestType
};
miningResultEventArgs.DbContext.ApiUserRequests.Add(apiRequest);
await miningResultEventArgs.DbContext.SaveChangesAsync();
By using SaveChanges instead of SaveChangesAsync everything works.
My only idea is to create another dbContext by passing the previous DbContext's SQL connection string
var dbOptions = new DbContextOptionsBuilder<PartnerFiscalNumberContext>();
dbOptions.UseSqlServer(miningResultEventArgs.DbContext.Database.GetDbConnection().ConnectionString);
using (var dbContext = new PartnerFiscalNumberContext(dbOptions.Options))
{
var appId = miningResultEventArgs.AppId;
var requestJson = miningResultEventArgs.RequestJson;
var typeOfQuery = miningResultEventArgs.TypeOfQuery;
var requestType = await dbContext.RequestType.FirstAsync(x => x.Id == typeOfQuery);
var apiUserRequester = await dbContext.ApiUsers.FirstAsync(x => x.AppId == appId);
var apiRequest = new ApiUserRequest()
{
ApiUser = apiUserRequester,
RequestJson = requestJson,
RequestType = requestType
};
dbContext.ApiUserRequests.Add(apiRequest);
await dbContext.SaveChangesAsync();
}
The latter code excerpt is just a small test to check my supposition, basically I should pass the SQL connection string instead of the DbContext object.
I am not sure (in terms of best practice) if I should pass a connection string and create a new dbContext object (and dispose it by using a using clause) or if I should use/have another mindset for this issue.
From what I know, using a DbContext should be done for a limited set of operations and not for multiple purposes.
EDIT 01
I'm going to detail more thorough what I've been doing down below.
I think I got an idea of why this error happens.
I have 2 controllers
One that receives a JSON and after de-serializing it I return a JSON to the caller and another controller that gets a JSON that encapsulates a list of objects that I iterate in an async way, returning an Ok() status.
The controllers are declared as async Task<IActionResult> and both feature an async execution of 2 similar methods.
The first one that returns a JSON executes this method
await ProcessFiscalNo(requestFiscalView.FiscalNo, dbContext);
The second one (the one that triggers this error)
foreach (string t in requestFiscalBulkView.FiscalNoList)
await ProcessFiscalNo(t, dbContext);
Both methods (the ones defined previously) start an event OnOperationComplete()
Within that method I execute the code from my post's beginning.
Within the ProcessFiscalNo method I DO NOT use any using contexts nor do I dispose the dbContext variable.
Within this method I only commit 2 major actions either updating an existing sql row or inserting it.
For edit contexts I select the row and tag the row with the modified label by doing this
dbContext.Entry(partnerFiscalNumber).State = EntityState.Modified;
or by inserting the row
dbContext.FiscalNumbers.Add(partnerFiscalNumber);
and finally I execute an await dbContext.SaveChangesAsync();
The error always gets triggered within the EventHandler ( the one detailed # the beginning of the thread) during the await dbContext.SaveChangedAsync()
which is pretty weird since 2 lines before that I do await reads on my DB with EF.
var requestType = await dbContext.RequestType.FirstAsync(x => x.Id == typeOfQuery);
var apiUserRequester = await dbContext.ApiUsers.FirstAsync(x => x.AppId == appId);
dbContext.ApiUserRequests.Add(new ApiUserRequest() { ApiUser = apiUserRequester, RequestJson = requestJson, RequestType = requestType });
//this throws the error
await dbContext.SaveChangesAsync();
For some reason calling await within the Event Handler notifies the caller to dispose the DbContext object.
Also by re-creating the DbContext and not re-using the old one I see a huge improvement on access.
Somehow when I use the first controller and return the info the DbContext object appears to get flagged by the CLR for disposal but for some unknown reason it still functions.
EDIT 02
Sorry for the bulk-ish content that follows, but I've placed all of the areas where I do use dbContext.
This is how I'm propagating my dbContext to all my controllers that request it.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMemoryCache();
// Add framework services.
services.AddOptions();
var connection = #"Server=.;Database=CrawlerSbDb;Trusted_Connection=True;";
services.AddDbContext<PartnerFiscalNumberContext>(options => options.UseSqlServer(connection));
services.AddMvc();
services.AddAuthorization(options =>
{
options.AddPolicy("PowerUser",
policy => policy.Requirements.Add(new UserRequirement(isPowerUser: true)));
});
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<IAuthorizationHandler, UserTypeHandler>();
}
In Configure I'm using the dbContext for my custom MiddleWare
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
var context = app.ApplicationServices.GetService<PartnerFiscalNumberContext>();
app.UseHmacAuthentication(new HmacOptions(),context);
app.UseMvc();
}
In the custom MiddleWare I'm only using it for a query.
public HmacHandler(IHttpContextAccessor httpContextAccessor, IMemoryCache memoryCache, PartnerFiscalNumberContext partnerFiscalNumberContext)
{
_httpContextAccessor = httpContextAccessor;
_memoryCache = memoryCache;
_partnerFiscalNumberContext = partnerFiscalNumberContext;
AllowedApps.AddRange(
_partnerFiscalNumberContext.ApiUsers
.Where(x => x.Blocked == false)
.Where(x => !AllowedApps.ContainsKey(x.AppId))
.Select(x => new KeyValuePair<string, string>(x.AppId, x.ApiHash)));
}
In my controller's CTOR I'm passing the dbContext
public FiscalNumberController(PartnerFiscalNumberContext partnerContext)
{
_partnerContext = partnerContext;
}
This is my Post
[HttpPost]
[Produces("application/json", Type = typeof(PartnerFiscalNumber))]
[Consumes("application/json")]
public async Task<IActionResult> Post([FromBody]RequestFiscalView value)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
var partnerFiscalNo = await _fiscalNoProcessor.ProcessFiscalNoSingle(value, _partnerContext);
}
Within the ProcessFiscalNoSingle method I have the following usage, If that partner exists then I'll grab him, if not, create and return him.
internal async Task<PartnerFiscalNumber> ProcessFiscalNoSingle(RequestFiscalView requestFiscalView, PartnerFiscalNumberContext dbContext)
{
var queriedFiscalNumber = await dbContext.FiscalNumbers.FirstOrDefaultAsync(x => x.FiscalNo == requestFiscalView.FiscalNo && requestFiscalView.ForceRefresh == false) ??
await ProcessFiscalNo(requestFiscalView.FiscalNo, dbContext, TypeOfQuery.Single);
OnRequestCompleted(typeOfQuery: (int)TypeOfQuery.Single, dbContextConnString: dbContext.Database.GetDbConnection().ConnectionString, requestJson: JsonConvert.SerializeObject(requestFiscalView), appId: requestFiscalView.RequesterAppId);
return queriedFiscalNumber;
}
Further down in the code, there's the ProcessFiscalNo method where I use the dbContext
var existingItem =
dbContext.FiscalNumbers.FirstOrDefault(x => x.FiscalNo == partnerFiscalNumber.FiscalNo);
if (existingItem != null)
{
var existingGuid = existingItem.Id;
partnerFiscalNumber = existingItem;
partnerFiscalNumber.Id = existingGuid;
partnerFiscalNumber.ChangeDate = DateTime.Now;
dbContext.Entry(partnerFiscalNumber).State = EntityState.Modified;
}
else
dbContext.FiscalNumbers.Add(partnerFiscalNumber);
//this gets always executed at the end of this method
await dbContext.SaveChangesAsync();
Also I've got an Event called OnRequestCompleted() where I pass my actual dbContext (after it ends up with SaveChangesAsync() if I update/create it)
The way I initiate the event args.
RequestCompleted?.Invoke(this, new MiningResultEventArgs()
{
TypeOfQuery = typeOfQuery,
DbContextConnStr = dbContextConnString,
RequestJson = requestJson,
AppId = appId
});
This is the notifier class (where the error occurs)
internal class RequestNotifier : ISbMineCompletionNotify
{
public async void UploadRequestStatus(object source, MiningResultEventArgs miningResultArgs)
{
await RequestUploader(miningResultArgs);
}
/// <summary>
/// API Request Results to DB
/// </summary>
/// <param name="miningResultEventArgs">EventArgs type of a class that contains requester info (check MiningResultEventArgs class)</param>
/// <returns></returns>
private async Task RequestUploader(MiningResultEventArgs miningResultEventArgs)
{
//ToDo - fix the following bug : Not being able to re-use the initial DbContext (that's being used in the pipeline middleware and controller area),
//ToDo - basically I am forced by the bug to re-create the DbContext object
var dbOptions = new DbContextOptionsBuilder<PartnerFiscalNumberContext>();
dbOptions.UseSqlServer(miningResultEventArgs.DbContextConnStr);
using (var dbContext = new PartnerFiscalNumberContext(dbOptions.Options))
{
var appId = miningResultEventArgs.AppId;
var requestJson = miningResultEventArgs.RequestJson;
var typeOfQuery = miningResultEventArgs.TypeOfQuery;
var requestType = await dbContext.RequestType.FirstAsync(x => x.Id == typeOfQuery);
var apiUserRequester = await dbContext.ApiUsers.FirstAsync(x => x.AppId == appId);
var apiRequest = new ApiUserRequest()
{
ApiUser = apiUserRequester,
RequestJson = requestJson,
RequestType = requestType
};
dbContext.ApiUserRequests.Add(apiRequest);
await dbContext.SaveChangesAsync();
}
}
}
Somehow when the dbContext reaches the Event Handler CLR gets notified to dispose the dbContext object (because I'm using await?)
Without recreating the object I was having huge lag when I wanted to use it.
While writing this I have an idea, I did upgrade my solution to 1.1.0 and I'm gonna try to see if it behaves similarly.
Concerning Why you get the error
As pointed out at the Comments by #set-fu DbContext is not thread safe.
In addition to that, since there is no explicit lifetime management of your DbContext your DbContext is going to get disposed when the garbage collector sees fit.
Judging from your context, and your mention about Request scoped DbContext
I suppose you DI your DbContext in your controller's constructor.
And since your DbContext is request scoped it is going to be disposed as soon as your Request is over,
BUT since you have already fired and forgot your OnRequestCompleted events there is no guarantee that your DbContext won't be disposed.
From there on , the fact that one of our methods succeeds and the other fails i think is seer "Luck".
One method might be faster than the other and completes before the Garbage collector disposes the DbContext.
What you can do about this is to change the return type of your Events from
async void
To
async Task<T>
This way you can wait your RequestCompleted Task within your controller to finish and that will guarantee you that your Controller/DbContext will not get Disposed until your RequestCompleted task is finished.
Concerning Properly handling DbContexts
There are two contradicting recommendations here by microsoft and many people use DbContexts in a completely divergent manner.
One recommendation is to "Dispose DbContexts as soon as posible"
because having a DbContext Alive occupies valuable resources like db
connections etc....
The other states that One DbContext per request is highly
reccomended
Those contradict to each other because if your Request is doing a lot of unrelated to the Db stuff , then your DbContext is kept for no reason.
Thus it is waste to keep your DbContext alive while your request is just waiting for random stuff to get done...
So many people who follow rule 1 have their DbContexts inside their "Repository pattern" and create a new Instance per Database Query
public User GetUser(int id)
{
User usr = null;
using (Context db = new Context())
{
usr = db.Users.Find(id);
}
return usr;
}
They just get their data and dispose the context ASAP.
This is considered by MANY people an acceptable practice.
While this has the benefits of occupying your db resources for the minimum time it clearly sacrifices all the UnitOfWork and "Caching" candy EF has to offer.
So Microsoft's recommendation about using 1 Db Context per request it's clearly based on the fact that your UnitOfWork is scoped within 1 request.
But in many cases and i believe your case also this is not true.
I consider Logging a separate UnitOfWork thus having a new DbContext for your Post-Request Logging is completely acceptable (And that's the practice i also use).
An Example from my project i have 3 DbContexts in 1 Request for 3 Units Of Work.
Do Work
Write Logs
Send Emails to administrators.

Cached values returned after TransactionScope.Dispose()

I'm a newbie in integration tests and I'm looking for some explanation and advice about workaround of my issue:
I'm using TransactionScope in my tests to keep database clean and creating new TransactionScope before each test and dispose it after each test:
[SetUp]
public void Init()
{
this.scope = new TransactionScope(
TransactionScopeOption.Required,
new TransactionOptions() { IsolationLevel = IsolationLevel.ReadUncommitted },
TransactionScopeAsyncFlowOption.Enabled);
this.context = new SportsPerformanceDbContext();
this.questRepo = new QuestionRepository(this.context);
}
[TearDown]
public void CleanAll()
{
this.context.Dispose();
this.scope.Dispose();
}
Everything works ok when I run one test class. But when I run at least two test classes, I'm facing a problem: in this test (see below) lasrQuestionId equals the last question id from database - that's ok, but actualResultId equals the Id_of_the_last_added_question_in_tests_with_transaction_scope + 1:
[Test]
public async void AddAsyncTest()
{
// Arrange
var questionModel = new QuestionModel
{
//some properties
};
Question lastQuestion = this.GetLastQuestion();
var lastQuestionId = lastQuestion?.Id ?? 0;
// Act
var addResult = await this.questRepo.AddAsync(questionModel);
var actualResult = addResult.Value;
// Assert
Assert.AreEqual(lastQuestionId + 1, actualResult.Id);
// some other assertions
}
So I have the following, e.g., lastQuestionId is 5 (5 questions in Database), but actualResult Id is 16 (because I've previously added some questions in other tests)... I assume that there is a problem with my context or scope.dispose(). I don't know where is a problem, could you explain what am I doing wrong here? Thanks in advance!
this.GetLastQuestion() code is below:
private Question GetLastQuestion()
{
using (var ctx = new SportsPerformanceDbContext())
{
return ctx.Question
.OrderByDescending(q => q.Id)
.FirstOrDefault();
}
}
This is how SQL Server engine works:
For a given identity property with specific seed/increment, the identity values are not reused by the engine. If a particular insert statement fails or if the insert statement is rolled back then the consumed identity values are lost and will not be generated again. This can result in gaps when the subsequent identity values are generated.

Mocking an attribute change on a parameter - using Moq

I am using Moq to mock my Repository layer so I can unit test.
My repository layer Insert methods update the Id property of my entities when a successful db insert occurs.
How do I configure moq to update the Id property of the entity when the Insert method is called?
Repository code:-
void IAccountRepository.InsertAccount(AccountEntity account);
Unit Test:-
[TestInitialize()]
public void MyTestInitialize()
{
accountRepository = new Mock<IAccountRepository>();
contactRepository = new Mock<IContactRepository>();
contractRepository = new Mock<IContractRepository>();
planRepository = new Mock<IPlanRepository>();
generator = new Mock<NumberGenerator>();
service = new ContractService(contractRepository.Object, accountRepository.Object,
planRepository.Object, contactRepository.Object, generator.Object);
}
[TestMethod]
public void SubmitNewContractTest()
{
// Setup Mock Objects
planRepository
.Expect(p => p.GetPlan(1))
.Returns(new PlanEntity() { Id = 1 });
generator
.Expect(p => p.GenerateAccountNumber())
.Returns("AC0001");
// Not sure what to do here?
// How to mock updating the Id field for Inserts?
//
// Creates a correctly populated NewContractRequest instance
NewContractRequest request = CreateNewContractRequestFullyPopulated();
NewContractResponse response = service.SubmitNewContract(request);
Assert.IsTrue(response.IsSuccessful);
}
implementation snippet from ContractService class (WCF service contract).
AccountEntity account = new AccountEntity()
{
AccountName = request.Contact.Name,
AccountNumber = accountNumber,
BillingMethod = BillingMethod.CreditCard,
IsInvoiceRoot = true,
BillingAddressType = BillingAddressType.Postal,
ContactId = request.Contact.Id.Value
};
accountRepository.InsertAccount(account);
if (account.Id == null)
{
// ERROR
}
I apologise if this information may be a little lacking. I only started learning moq and mocking frameworks today. ac
You can use the Callback method to mock side-effects. Something like:
accountRepository
.Expect(r => r.InsertAccount(account))
.Callback(() => account.ID = 1);
That's untested but it's along the right lines.
I'm not sure how that will work because account is created inside the method, so it's not the instance I'll be referring to when I set the value of ID to 1.
Perhaps there's a flaw in my design and I should be checking for ID > 0 inside the IAccountRepository.InsertAccount implementation and then returning a bool if it's ok. Though then I've a problem with inserting an account that has a related object that needs to be insterted (and an Id genereated).
I found this to be the answer to my problem
accountRepository
.Expect(p => p.InsertAccount(It.Is<AccountEntity>(x => x.Id == null)))
.Callback<AccountEntity>(a => a.Id = 1);
thanks.

Categories

Resources