0

I am unit testing ABP, but I got error below:

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. Object name: 'XXXDbContext'.

Here are my detailed steps:

  1. AppService

    public async Task<ProductDto> CreateProduct(CreateProductInput input)
    {
        var existing = // await _productManager.ProductRepository.FirstOrDefaultAsync(p => p.Name == input.Name);
                          await _productManager.Products.Where(p => p.Name == input.Name).FirstOrDefaultAsync();
        if (existing != null) throw new UserFriendlyException(L("ExistedRepeatedAd"));
    
        var newAdEntity = ObjectMapper.Map<Product>(input);
        // Rest of the code
    }
    
  2. ProductManager

    public class ProductManager : IDomainService
    {
        private readonly IRepository<Product, long> _ProductRepository;
        private readonly IUnitOfWorkManager _unitOfWorkManager;
    
        public ProductsManager(
            IRepository<Product, long> ProductRepository,
            IUnitOfWorkManager unitOfWorkManager)
        {
            _ProductRepository = ProductRepository;
            _unitOfWorkManager = unitOfWorkManager;
        }
    
        #region Products
    
        public IRepository<Product, long> ProductRepository
        {
            get { return _ProductRepository; }
        }
    
        public IQueryable<Product> Products
        {
            get { return _ProductRepository.GetAll(); }
        }
    
        public async Task<Product> CreateProduct(Product input)
        {
            var result = await _ProductRepository.InsertAsync(input);
            await _unitOfWorkManager.Current.SaveChangesAsync();
            return result;
        }
    
        #endregion
    }
    

It will throw error this line:

await _adManager.Ads.Where(p => p.Name == input.Name).FirstOrDefaultAsync();

But if I use this instead, it will work:

await _adManager.AdRepository.FirstOrDefaultAsync(p => p.Name == input.Name);

In addition, I get _unitOfWorkManager.Current as null in the above code.
Is there any suggestion?

aaron
  • 39,695
  • 6
  • 46
  • 102
Edward
  • 28,296
  • 11
  • 76
  • 121
  • You declare this as Unit Testing right? Unless you do integration testing, there should be no dependency injection or other services involved. Unit Tests are supposed to provide a proof that implemented functionality works as desired in full isolation. The idea is that you test all the functionality separately, and then put them together in Integration Test. Otherwise every time you test something, there's a chance for other services to cause failure, hence the tests are unpredictable. – yoger Apr 14 '18 at 06:47
  • Is there any demo with Integration testing for ABP? – Edward Apr 14 '18 at 08:22
  • It depends which part do you want to test. Sometimes it makes sense to test data access layer. Maintaining complete application tests is kind of harsh. Kind of acceptance tests, that you verify with automated UI tests. These may be useful in critical areas of your application, but introducing them too early, when the application changes a lot may be too expensive to maintain. Unit Testing with tests written in TDD manner combined with some additional release candidate regression tests is usually enough. – yoger Apr 15 '18 at 12:50

2 Answers2

3

UnitOfWork Attribute

Add [UnitOfWork] attribute and make it a virtual method:

[UnitOfWork]
public virtual async Task<ProductDto> CreateProduct(CreateProductInput input)
{
    var existing = await _productManager.Products
        .Where(p => p.Name == input.Name)
        .FirstOrDefaultAsync();

    // ...
}
[UnitOfWork]
public virtual async Task<Product> CreateProduct(Product input)
{
    var result = await _ProductRepository.InsertAsync(input);
    await _unitOfWorkManager.Current.SaveChangesAsync();
    return result;
}

See: UnitOfWork Attribute Restrictions

You can use UnitOfWork attribute for:

  • All public or public virtual methods for classes that are used over an interface (Like an application service used over a service interface).
  • All public virtual methods for self-injected classes (Like MVC Controllers and Web API Controllers).
  • All protected virtual methods.

IUnitOfWorkManager

You can inject IUnitOfWorkManager to begin a UnitOfWork explicitly:

public async Task<Product> CreateProduct(Product input)
{
    using (var uow = _unitOfWorkManager.Begin())
    {
        var result = await _ProductRepository.InsertAsync(input);
        await _unitOfWorkManager.Current.SaveChangesAsync();

        await uow.CompleteAsync();

        return result;
    }
}
aaron
  • 39,695
  • 6
  • 46
  • 102
1

My issue is resolved by creating Interface for my appservice and then Resolve this Interface in my test projects.

Thanks for the suggestion from aaron, but it would be complex to using uniofwork in my every application service.

Edward
  • 28,296
  • 11
  • 76
  • 121