MyModel objRecord = new MyModel();
objRecord.ID = 42;
objRecord.Name = "Dave";
_context.CaseComplainants.Add(objRecord);
await _context.SaveChangesAsync();
List<MyOtherModel> lstRecord = new List<MyOtherModel>();
lstRecord = await _context.MyOthers
.Include(p => p.Title)
.Where(n => n.MyOtherID == objRecord.ID)
.ToListAsync();
This code works fine if it's all in the same method. When I move the second chunk into it's own method I get the error message
System.ObjectDisposedException: 'Cannot access a disposed object. A
common cause of this error is disposing a context that was resolved
from dependency injection and then later trying to use the same
context instance elsewhere in your application. This may occur if you
are calling Dispose() on the context, or wrapping the context in a
using statement. If you are using dependency injection, you should let
the dependency injection container take care of disposing context
instances. ObjectDisposed_ObjectName_Name'
The _context is class wide and hovering over it shows what you'd expect.
The model looks like this.
public class MyOtherModel {
[ForeignKey("ID")]
public int ID { get; set; }
[ForeignKey("TitleID")]
public int? TitleID { get; set; }
public virtual LookUp Title { get; set; }
I've looked into the error message and all the problems that I can find are to do with the general set up of the EF infrastructure, one way or another. I can't find one that talks about anything non-infrastructure related.
Any ideas why moving the code into another method breaks it?
Edit
The context is initialised in the boilerplate ConfigureServices, straight out of the example documentation
public void ConfigureServices(IServiceCollection services) {
services.AddDbContext<MyContextModel>(options => options.UseSqlServer(Configuration.GetConnectionString("MyModel")));
This works
public async Task<IActionResult> OnPostAsync({
MyModel objRecord = new MyModel();
objRecord.ID = 42;
objRecord.Name = "Dave";
_context.CaseComplainants.Add(objRecord);
await _context.SaveChangesAsync();
List<MyOtherModel> lstRecord = new List<MyOtherModel>();
lstRecord = await _context.MyOthers
.Include(p => p.Title)
.Where(n => n.MyOtherID == objRecord.ID)
.ToListAsync();
This doesn't
public async Task<IActionResult> OnPostAsync({
MyModel objRecord = new MyModel();
objRecord.ID = 42;
objRecord.Name = "Dave";
_context.CaseComplainants.Add(objRecord);
await _context.SaveChangesAsync();
DoStuff(objRecord.ID);
}
private async void DoStuff(int TheID) {
List<MyOtherModel> lstRecord = new List<MyOtherModel>();
lstRecord = await _context.MyOthers
.Include(p => p.Title)
.Where(n => n.MyOtherID == TheID)
.ToListAsync();
Related
I'm trying to write a test for a service that uses IStringLocalizerFactory to translate strings. All of the translations are in a single Resource file. I cannot seem to get the Mock for it to work, as it always throws a NullReferenceException. When debugging, it shows that _localizer is null. When I remove the localizer logic completely, the test succeeds.
Code I'm trying to test:
private readonly IStringLocalizer _localizer;
public EventService(IEventRepository eventRepository, IMemberEventRepository memberEventRepository, IStringLocalizerFactory factory)
{
this._eventRepository = eventRepository;
this._memberEventRepository = memberEventRepository;
this._localizer = factory.Create(typeof(Resource));
}
public async Task CreateEventRegistrationAsync(MemberEvent entity)
{
if (await this._memberEventRepository.GetMemberEvent(entity.EventId, entity.MemberId) != null)
{
throw new ArgumentException(_localizer["This member already participates in this event."].Value);
}
await this._memberEventRepository.CreateAsync(entity);
}
My tests:
private Mock<IStringLocalizerFactory> _stringLocalizerFactoryMock = new Mock<IStringLocalizerFactory>();
public EventServiceTests()
{
_service = new EventService(_eventRepoMock.Object, _memberEventRepoMock.Object, _stringLocalizerFactoryMock.Object);
}
[Fact]
public async Task CreateEventRegistrationAsync_ShouldThrowArgumentException_WhenMemberAlreadyRegisteredForEvent()
{
int eventId = 456;
int memberId = 123;
_stringLocalizerFactoryMock.Setup(x => x.Create(typeof(Resource)))
.Returns(() => new StringLocalizer<Resource>(_stringLocalizerFactoryMock.Object));
MemberEvent registration = new MemberEvent
{
EventId = eventId,
MemberId = memberId
};
_memberEventRepoMock.Setup(x => x.GetMemberEvent(eventId, memberId))
.ReturnsAsync(registration);
await Assert.ThrowsAsync<ArgumentException>(async () => await _service.CreateEventRegistrationAsync(registration));
}
From examining the subject under test I see that
_localizer["This member already participates in this event."].Value
will throw a null exception because _localizer[...] was not setup and thus fail when .Value is invoked.
Consider mocking a IStringLocalizer<T> so that it can be setup to behave as expected when the test is invoked.
//...
var localizer = new Mock<IStringLocalizer<Resource>>();
localizer
.Setup(_ => _[It.IsAny<string>()])
.Returns(string key => new LocalizedString(key, key));
_stringLocalizerFactoryMock
.Setup(_ => _.Create(It.IsAny<Type>()))
.Returns(() => localizer.Object));
//...
I started using xUnit and NSubstitute for my unit tests. I want to mock the following method.
public async Task<DecorationModel> GetDecorationWithId(string userId, string decorationId)
{
var decoration = await _db.Decorations
.Include(d => d.BgImage)
.FirstOrDefaultAsync(d => d.Id == decorationId);
if (decoration == null || decoration.OwnerId != userId)
return null;
return new DecorationModel
{
Id = decoration.Id,
Name = decoration.Name,
// Other stuff
};
}
I attempted it but couldn't get it to work. My current test class is as follows;
public class DecorationServiceTests
{
private readonly DecorationService _subject;
private readonly IAppDbContext _db = Substitute.For<IAppDbContext>();
private readonly DbSet<Decoration> _decorationDbSet = Substitute.For<DbSet<Decoration>, IQueryable<Decoration>>();
public DecorationServiceTests()
{
_subject = new DecorationService(_db);
}
[Fact]
public async Task GetDecorationWithId_ShouldReturnDecoration_WhenExists()
{
// Arrange
var userId = new Guid().ToString();
var decorationId = new Guid().ToString();
var decorations = new List<Decoration>()
{
new Decoration()
{
Id = decorationId,
Name = "",
OwnerId = userId,
}
};
_db.Decorations.Returns(_decorationDbSet);
_decorationDbSet.FirstOrDefaultAsync(t => t.Id == decorationId).Returns(decorations.FirstOrDefault());
// Act
var result = await _subject.GetDecorationWithId(userId, decorationId);
// Assert
Assert.Equal(result.Id, decorations[0].Id);
}
}
However, I get the following error:
"The provider for the source 'IQueryable' doesn't implement 'IAsyncQueryProvider'. Only providers that implement 'IAsyncQueryProvider' can be used for Entity Framework asynchronous operations."
I searched on the web but couldn't find a good reference. How can I solve this?
I think you'll be going through a whole lot of pain and suffering if you are trying to mock DbSet. That comes straight from the docs of EFCore: https://learn.microsoft.com/en-us/ef/core/testing/#unit-testing
Instead, you should be trying to use a real db or in-memory one.
See the testing sample here: https://learn.microsoft.com/en-us/ef/core/testing/testing-sample
I am writing unit tests for my my Web API and cannot get the test to pass except by removing the include (eager-loading from the method). I am using the in-memory database to provide the dbcontext and can't figure out why it is returning no data. Thanks in advance for any help or constructive criticism
This is the method I am trying to test.
Note: it passes the test if I comment out the .include statements.
public async Task<LibraryAsset> GetAsset(int assetId)
{
var asset = await _context.LibraryAssets
.Include(p => p.Photo)
.Include(p => p.Category)
.Include(a => a.AssetType)
.Include(s => s.Status)
.Include(s => s.Author)
.FirstOrDefaultAsync(x => x.Id == assetId);
return asset;
}
This is the base DbContext using the inMemory DB:
public DataContext GetDbContext()
{
var builder = new DbContextOptionsBuilder<DataContext>();
if (useSqlite)
{
// Use Sqlite DB.
builder.UseSqlite("DataSource=:memory:", x => { });
}
else
{
// Use In-Memory DB.
builder.UseInMemoryDatabase(Guid.NewGuid().ToString());
}
var DataContext = new DataContext(builder.Options);
if (useSqlite)
{
// SQLite needs to open connection to the DB.
// Not required for in-memory-database and MS SQL.
DataContext.Database.OpenConnection();
}
DataContext.Database.EnsureCreated();
return DataContext;
}
This is the test:
[Fact]
public async void GetAssetById_ExistingAsset_ReturnAsset()
{
using (var context = GetDbContext())
{
ILogger<LibraryAssetService> logger = new
NullLogger<LibraryAssetService>();
var service = new LibraryAssetService(context, _logger);
var asset = new LibraryAsset
{
Id = 40,
NumberOfCopies = 20,
Title = "",
Year = 1992,
Status = new Status { Id = 1 },
AssetType = new AssetType { Id = 1 },
Author = new Author { Id = 1 },
Category = new Category { Id = 2 },
Photo = new AssetPhoto { Id = 1 }
};
context.LibraryAssets.Attach(asset);
context.Add(asset);
context.SaveChanges();
var actual = await service.GetAsset(40);
Assert.Equal(40, actual.Id);
}
}
This is my first time writing unit tests and I am basically learning as I go. Please feel free to point out any other mistakes that you may have noticed as well.
There are some issues with your code:
If your real databse is relational avoid using UseInMemoryDatabase database for testing because it doesn't support relational behaviours.
Separate the Arrange contexts from the Act contexts. That means, create a new DataContext for preparing the test, adding test data, and etc, and create another one for SUT (LibraryAssetService in this case). DbContext stores local data (in memory) which may not exist in the database and that could show fake green tests in some scenarios!
You don't need Attach when you're adding the assets. That could create Foreign key constraint error with sqlite.
I removed some of your navigations and parameters for the sake of simplicity. So lets suppose the LibraryAssetService is something like this:
public class LibraryAssetService
{
public LibraryAssetService(DataContext context)
{
_context = context;
}
private readonly DataContext _context;
public async Task<LibraryAsset> GetAsset(int assetId)
{
var asset = await _context.LibraryAssets
.Include(p => p.Photo)
.Include(s => s.Author)
.FirstOrDefaultAsync(x => x.Id == assetId);
return asset;
}
}
The test class:
public class LibraryAssetServiceTests
{
public LibraryAssetServiceTests()
{
_factory = new TestDataContextFactory();
}
private TestDataContextFactory _factory;
[Fact]
public async void GetAssetById_ExistingAsset_ReturnAsset()
{
// Arrange
using (var context = _factory.Create())
{
var asset = new LibraryAsset
{
Id = 40,
Author = new Author { Id = 1 },
Photo = new Photo { Id = 1 }
};
context.Add(asset);
context.SaveChanges();
}
// Act
using (var context = _factory.Create())
{
var service = new LibraryAssetService(context);
var actual = await service.GetAsset(40);
// Assert
Assert.Equal(40, actual.Id);
Assert.Equal(1, actual.Author.Id);
Assert.Equal(1, actual.Photo.Id);
}
}
}
And finally, a little helper class to prepare the DataContext for your tests. It's good practice to extract these kind of things outside your test classes. The important thing to remember when testing with sqlite memory databases is that you should keep the connection open during the test. No matter how many DbContext instances you create. The xUnit create an instance of the test class for each test method. So an instance of TestDataContextFactory will be created for each test, and you are good to go.
public class TestDataContextFactory
{
public TestDataContextFactory()
{
var builder = new DbContextOptionsBuilder<DataContext>();
var connection = new SqliteConnection("DataSource=:memory:");
connection.Open();
builder.UseSqlite(connection);
using (var ctx = new DataContext(builder.Options))
{
ctx.Database.EnsureCreated();
}
_options = builder.Options;
}
private readonly DbContextOptions _options;
public DataContext Create() => new DataContext(_options);
}
I'm using the new Razor Pages with core 2.0 and I'm trying to see if I can share the query code by putting the linq and db initialization into a Utility class in App_code folder but I'm having an issue with making this class to work.
Basically I'm querying the database with linq but I also have another asp-page-handler that will create a PDF.
Here is my page
public class IndexModel : PageModel
{
private readonly CrewLogContext _context;
//initialize db
public IndexModel(CrewLogContext context)
{
_context = context;
}
public IList<SelectionDailyReport> SelectionDailyReport { get; set; }
public DateSearchRange DateSearchRange { get; set; }
public async Task OnPostSelectionAsync(DateSearchRange DateSearchRange)
{
var result= await _context.TaskSelection.Where(x => x.WorkDate >= startDate && x.WorkDate <= endDate)
.Include(t => t.Zone).OrderBy(x => x.AssocId).ToListAsync();
SelectionDailyReport = result;
}
OK. so this works fine. I also have a another button that calls a webApi controller that will basically query the same data and generates a PDF as well.
I wanted to create a utility class and call its method from the page or the webapi controller so I don't have to write the linq query twice.
I created an App_Folder and add a .cs file
in Utility.cS
namespace WebCrewLog.App_Code
{
public class Utility
{
private CrewLogContext _context;
//initiate db connection
public Utility(CrewLogContext context)
{
_context = context;
}
public IList<SelectionDailyReport> GetSelectionData (DateSearchRange DateSearchRange)
{
var myList = _context.TaskSelection.Where(x => x.WorkDate >=DateSearchRange.StartDate && x.WorkDate <= DateSearchRange.EndDate)
.Include(t => t.Zone).OrderBy(x => x.AssocId).ToList();
return myList ;
}
Then from my page or my webapi I wanted to call
SelectionDailyReport = Utility.GetSelectionData(DateSearchRange);
So the problem is that intellisense does not detect the GetSelectionData method. So I thought I should make the GetSelectionData method static.
so Change Utility Class
public IList<SelectionDailyReport> GetSelectionData (DateSearchRange DateSearchRange)
{
var myList = _context.TaskSelection.Where(x => x.WorkDate >=DateSearchRange.StartDate && x.WorkDate <= DateSearchRange.EndDate)
.Include(t => t.Zone).OrderBy(x => x.AssocId).ToList();
....
public static IList<SelectionDailyReport> GetSelectionData (DateSearchRange DateSearchRange)
{
var myList = _context.TaskSelection.Where(x => x.WorkDate >=DateSearchRange.StartDate && x.WorkDate <= DateSearchRange.EndDate)
.Include(t => t.Zone).OrderBy(x => x.AssocId).ToList();
....
However, when I do that I'm getting a null exception on _context.TaskSelection and not sure why is that.
The problem is that when you changed the function to be static, now calling it does not initialize the class and the constructor public Utility(CrewLogContext context) is not called, so the _context field is not assigned to anything and stays null.
One way to solve this is to remove static and call the function after building the object and passing the context. So instead of:
SelectionDailyReport = Utility.GetSelectionData(DateSearchRange);
Try something like:
Utility u = new Utility(myContext); //Change myContext to the name of your context
SelectionDailyReport = u.GetSelectionData(DateSearchRange);
If you want it to be static, then you'll have to pass it the context:
public static IList<SelectionDailyReport> GetSelectionData (DateSearchRange DateSearchRange, CrewLogContext context) {
var myList = context.TaskSelection.Where(x => x.WorkDate >=DateSearchRange.StartDate && x.WorkDate <= DateSearchRange.EndDate)
.Include(t => t.Zone).OrderBy(x => x.AssocId).ToList();
return myList ;
}
The above solutions may seem easier, but it's probably better to use the proper injection just like how it works in the controllers.
I'm reading a lot about Entity Framework async lately, and I'm looking for the correct way to change my repositories to async versions. They now look more or less as follows:
public class MyRepo
{
private DbContext _context;
public MyRepo()
{
_context = new DbContext();
}
public int DoSomeStuff(int id)
{
var item = _context.MyModels.Where(x => x.Id == id).SingleOrDefault();
item.MyProperty = "abcd";
return _context.SaveChanges();
}
}
Example of calling this repository:
var myRepo = new MyRepo();
myRepo.DoSomeStuff(1);
myRepo.DoSomeStuff(2);
Can I just change the DoSomeStuff function to:
public async Task<int> DoSomeStuffAsync(int id)
{
var item = await _context.MyModels.Where(x => x.Id == id).SingleOrDefaultAsync();
item.MyProperty = "abcd";
return await _context.SaveChangesAsync();
}
or is this the wrong way of handling the DbContext instance, and should it be:
public async Task<int> DoSomeStuffAsync(int id)
{
using(var db = new DbContext())
{
// ...
}
}
You can do it either way, depending on the semantics you want. If you're using a shared DbContext, you just have to document that so your callers will only call one asynchronous method at a time.