You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

EF Core DbContext包装类单元测试空引用异常解决方法咨询

问题描述

我封装了EF Core DbContext的DatabaseConnector类,在Rest API控制器中正常工作,但编写单元测试时触发Object reference not set to an instance of an object(空引用异常)。相关代码如下:

DatabaseConnector类代码

using System.Diagnostics;
using Microsoft.EntityFrameworkCore;
using DatabaseProvider.Interfaces;

namespace DatabaseProvider.Classes
{
    public class DatabaseConnector(DbContext context) : IDatabaseConnector
    {
        private readonly DbContext _context = context;

        public async Task<List<T>> GetAsync<T>() where T : class
        {
            try
            {
                return await _context.Set<T>().AsQueryable().ToListAsync();
            }
            catch (Exception exc)
            {
                Debug.Write(exc.Message);
                throw;
            }
        }

        public async Task<T?> GetByIdAsync<T>(int id) where T : class
        {
            try
            {
                return await _context.FindAsync<T>(id);
            }
            catch (Exception exc)
            {
                Debug.Write(exc.Message);
                throw;
            }
        }

        public async Task<bool> CreateAsync<T>(T entity)
        {
            try
            {
                if (entity != null)
                {
                    _context.Add(entity);
                    await _context.SaveChangesAsync();

                    return true;
                }

                return false;
            }
            catch (Exception exc)
            {
                Debug.Write(exc.Message);
                return false;
            }
        }

        public async Task<bool> UpdateAsync<T>(T entity)
        {
            try
            {
                if (entity != null)
                {
                    _context.Update(entity);
                    await _context.SaveChangesAsync();

                    return true;
                }

                return false;
            }
            catch (Exception exc)
            {
                Debug.Write(exc.Message);
                return false;
            }
        }

        public async Task<bool> DeleteAsync<T>(T entity)
        {
            try
            {
                if (entity != null)
                {
                    _context.Remove(entity);
                    await _context.SaveChangesAsync();

                    return true;
                }

                return false;
            }
            catch (Exception exc)
            {
                Debug.Write(exc.Message);
                return false;
            }
        }
    }
}

单元测试类代码

using Moq;
using DatabaseProvider.Interfaces;
using DatabaseProvider.Models;
using DatabaseProvider.Classes;

namespace NUnitTests.DatabaseProvider
{
    public class DatabaseConnectorTests
    {
        private Mock<TestDbContext> _context;
        private IDatabaseConnector _connector;

        [SetUp]
        public void Setup()
        {
            _context = new Mock<TestDbContext>();

            var abouts = DatabaseDataFake.GetFakeAboutList();

            _context.Setup(x => x.Abouts).Returns(MockCreater.GetQueryableMockDbSet(abouts));

            _connector = new DatabaseConnector(_context.Object);
        }

        [Test]
        public async Task Get_About_List()
        {
            var result = await _connector.GetAsync<About>();

            Assert.That(result, Is.Not.Null);
            Assert.That(result, Has.Count.EqualTo(2));
        }
    }
}

MockDbSet创建类代码

using Moq;
using Microsoft.EntityFrameworkCore;

namespace NUnitTests
{
    public static class MockCreater
    {
        public static DbSet<T> GetQueryableMockDbSet<T>(List<T> sourceList) where T : class
        {
            var queryable = sourceList.AsQueryable();
            var dbSet = new Mock<DbSet<T>>();
            dbSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(queryable.Provider);
            dbSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(queryable.Expression);
            dbSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(queryable.ElementType);
            dbSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(queryable.GetEnumerator);
            dbSet.Setup(d => d.Add(It.IsAny<T>())).Callback<T>(sourceList.Add);
            return dbSet.Object;
        }
    }
}
解决方案

问题根因

单元测试中你Mock了TestDbContext.Abouts属性,但DatabaseConnector.GetAsync<T>方法调用的是DbContext.Set<T>()来获取实体集合,而非直接访问上下文的实体属性,导致Set<T>()返回null,触发空引用异常。此外,当前的MockDbSet未支持EF Core的异步查询方法(如ToListAsync()),即使解决了空引用,异步调用仍可能报错。

方法一:修正Moq配置

1. 调整测试类的Setup逻辑

将原来MockAbouts属性的代码,改为MockSet<About>()方法:

[SetUp]
public void Setup()
{
    _context = new Mock<TestDbContext>();

    var abouts = DatabaseDataFake.GetFakeAboutList();
    var aboutsDbSet = MockCreater.GetQueryableMockDbSet(abouts);

    // 改为Mock Set<T>方法,而非具体的DbSet属性
    _context.Setup(x => x.Set<About>()).Returns(aboutsDbSet);

    _connector = new DatabaseConnector(_context.Object);
}

2. 增强MockDbSet以支持异步查询

当前的MockDbSet仅实现了同步IQueryable接口,无法支持ToListAsync()等异步方法,需要补充异步相关的Mock:

更新MockCreater类,添加对IAsyncEnumerable和异步Provider的支持:

using Moq;
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq.Async; // 需要安装System.Linq.Async NuGet包

namespace NUnitTests
{
    public static class MockCreater
    {
        public static DbSet<T> GetQueryableMockDbSet<T>(List<T> sourceList) where T : class
        {
            var queryable = sourceList.AsQueryable();
            var dbSet = new Mock<DbSet<T>>();

            // 同步IQueryable配置
            dbSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(new AsyncQueryProvider<T>(queryable.Provider));
            dbSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(queryable.Expression);
            dbSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(queryable.ElementType);
            dbSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(queryable.GetEnumerator);

            // 异步IAsyncEnumerable配置
            dbSet.As<IAsyncEnumerable<T>>()
                 .Setup(m => m.GetAsyncEnumerator(It.IsAny<CancellationToken>()))
                 .Returns(new AsyncEnumerator<T>(queryable.GetEnumerator()));

            // Add方法配置
            dbSet.Setup(d => d.Add(It.IsAny<T>())).Callback<T>(sourceList.Add);

            return dbSet.Object;
        }

        // 自定义异步查询提供器
        private class AsyncQueryProvider<TEntity> : IAsyncQueryProvider
        {
            private readonly IQueryProvider _inner;

            public AsyncQueryProvider(IQueryProvider inner)
            {
                _inner = inner;
            }

            public IQueryable CreateQuery(Expression expression)
            {
                return new AsyncEnumerable<TEntity>(expression);
            }

            public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
            {
                return new AsyncEnumerable<TElement>(expression);
            }

            public object Execute(Expression expression)
            {
                return _inner.Execute(expression);
            }

            public TResult Execute<TResult>(Expression expression)
            {
                return _inner.Execute<TResult>(expression);
            }

            public Task<object> ExecuteAsync(Expression expression, CancellationToken cancellationToken)
            {
                return Task.FromResult(Execute(expression));
            }

            public Task<TResult> ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken)
            {
                return Task.FromResult(Execute<TResult>(expression));
            }
        }

        // 自定义异步枚举器
        private class AsyncEnumerable<TEntity> : EnumerableQuery<TEntity>, IAsyncEnumerable<TEntity>, IQueryable<TEntity>
        {
            public AsyncEnumerable(Expression expression) : base(expression) { }

            public IAsyncEnumerator<TEntity> GetAsyncEnumerator(CancellationToken cancellationToken = default)
            {
                return new AsyncEnumerator<TEntity>(this.AsEnumerable().GetEnumerator());
            }

            IQueryProvider IQueryable.Provider => new AsyncQueryProvider<TEntity>(this.Provider);
        }

        // 自定义异步枚举器实现
        private class AsyncEnumerator<TEntity> : IAsyncEnumerator<TEntity>
        {
            private readonly IEnumerator<TEntity> _inner;

            public AsyncEnumerator(IEnumerator<TEntity> inner)
            {
                _inner = inner;
            }

            public ValueTask DisposeAsync()
            {
                _inner.Dispose();
                return ValueTask.CompletedTask;
            }

            public ValueTask<bool> MoveNextAsync()
            {
                return ValueTask.FromResult(_inner.MoveNext());
            }

            public TEntity Current => _inner.Current;
        }
    }
}

方法二:改用EF Core内存数据库(更简单可靠)

手动Mock DbContext和DbSet容易遗漏方法,推荐使用EF Core的In-Memory数据库进行单元测试,无需复杂Mock:

1. 安装In-Memory数据库NuGet包

Install-Package Microsoft.EntityFrameworkCore.InMemory

2. 修改测试类的Setup逻辑

[SetUp]
public void Setup()
{
    // 创建内存数据库上下文
    var options = new DbContextOptionsBuilder<TestDbContext>()
        .UseInMemoryDatabase(databaseName: "TestDatabase")
        .Options;

    var context = new TestDbContext(options);
    // 初始化测试数据
    var abouts = DatabaseDataFake.GetFakeAboutList();
    context.Abouts.AddRange(abouts);
    context.SaveChanges();

    _connector = new DatabaseConnector(context);
}

这种方式完全模拟真实的EF Core行为,无需手动Mock任何方法,异步查询、增删改查都能正常工作,测试结果更可靠。


内容的提问来源于stack exchange,提问作者Kanasi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 07:44:57