【问题标题】:Mongodb c# Unit testing: Object reference not set to an instance of an objectMongodb c#单元测试:对象引用未设置为对象的实例
【发布时间】:2017-11-21 13:26:36
【问题描述】:

我正在尝试对我的 Workorders 的数据存储库进行单元测试。

存储库采用 DatabaseContext 实例,该实例访问 mongodb 数据库并公开工作订单的 IMongoCollection,如下所示。

我使用 Moq 来模拟 IMongoCollection 和 DatabaseContext。 在 DatabaseContext 的 Moq 设置中,我选择在调用公开的 Workorder 属性时返回 IMongoCollection。反过来,当 DeleteOneAsync 时,模拟的 IMongoCollection 返回一个 DeleteResult.Acknowledged(1) 任务 被调用。

通过这种方式,我希望在不需要运行数据库的情况下进行一些单元测试,但我遇到了一个错误,告诉我“System.NullReferenceException : Object reference not set to an instance of an object.”,当我尝试执行“return result.DeleteCount == 1”时,我指向一行,其中result 是返回的 DeleteResult 对象。

似乎我的结果没有正确创建,下面是我的代码。

错误本身 在运行测试时发生

Test Name:  MESAPITests.RepositoryTests.WorkorderRepositoryTests.DeleteWorkorderById_ReturnsBooleanTrue
Test FullName: MESAPITests.RepositoryTests.WorkorderRepositoryTests.DeleteWorkorderById_ReturnsBooleanTrue
Test Source:    C:\Users\Zacke\Documents\Repositories\MES-API\MES-API Tests\RepositoryTests\WorkorderRepositoryTests.cs : line 22
Test Outcome:   Failed
Test Duration:  0:00:00.354

Result StackTrace:  
at MESAPI.Repositories.WorkorderRepository.<DeleteWorkorderById>d__2.MoveNext() in C:\Users\Zacke\Documents\Repositories\MES-API\MES-API\Repositories\WorkRepositories\WorkorderRepository.cs:line 28
--- End of stack trace from previous location where exception was thrown ---    
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at MESAPITests.RepositoryTests.WorkorderRepositoryTests.<DeleteWorkorderById_ReturnsBooleanTrue>d__1.MoveNext() in C:\Users\Zacke\Documents\Repositories\MES-API\MES-API Tests\RepositoryTests\WorkorderRepositoryTests.cs:line 34
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
Result Message: System.NullReferenceException : Object reference not set to an instance of an object.

DataRepositoryTests

public class WorkorderRepositoryTests
{
    private WorkorderRepository _repo;

    [Fact]
    public async void DeleteWorkorderById_ReturnsBooleanTrue()
    {
        var mockCollection = new Mock<IMongoCollection<Workorder>>();
        mockCollection
            .Setup(_ => _.DeleteOneAsync(It.IsAny<Expression<Func<Workorder, bool>>>(),
                default(CancellationToken)))
            .ReturnsAsync(await Task.Run<DeleteResult>(() => new DeleteResult.Acknowledged(1)));

        var mockContext = new Mock<IDatabaseContext>();
        mockContext.Setup(_ => _.Workorders).Returns(mockCollection.Object);

        _repo = new WorkorderRepository(mockContext.Object);
        var id = ObjectId.GenerateNewId().ToString();
        var result = await _repo.DeleteWorkorderById(id);
        Assert.True(result);
    }
}

WorkorderRepository DeleteWorkorderById 第二行发生错误

public class WorkorderRepository : IWorkorderRepository
{
    private readonly IMongoCollection<Workorder> _workorders;

    public WorkorderRepository(IDatabaseContext context)
    {
        _workorders = context.Workorders;
    }

    public async Task<bool> DeleteWorkorderById(string id)
    {
        var result = await _workorders.DeleteOneAsync(w => w.Id == id);
        return result.DeletedCount == 1;
    }

    public async Task<List<Workorder>> GetAllWorkordersAsList()
    {
        return await _workorders.FindAsync(new BsonDocument()).Result.ToListAsync();
    }

    public async Task<Workorder> GetWorkorderById(string id)
    {
        return await _workorders.FindAsync(w => w.Id == id).Result.FirstOrDefaultAsync();
    }

    public async Task<Workorder> PostNewWorkroder(WorkorderPost workorderPost)
    {
        var newWorkorder = new Workorder(workorderPost);
        await _workorders.InsertOneAsync(newWorkorder);
        return await _workorders.FindAsync(w => w.Id == newWorkorder.Id).Result.FirstOrDefaultAsync();
    }

    public async Task<bool> UpdateWorkorder(Workorder workorder)
    {
        var result = await _workorders.ReplaceOneAsync(w => w.Id == workorder.Id, workorder);
        return result.MatchedCount != 0;
    }
}

IDatabaseRepository

public interface IDatabaseContext
{
    /// <summary>
    /// Mongo database context.
    /// </summary>
    IMongoDatabase Database { get; set; }

    /// <summary>
    /// BomFamily database context.
    /// </summary>
    IMongoCollection<BomFamily> BomFamilies { get; set; }

    /// <summary>
    /// BomGroup database context.
    /// </summary>
    IMongoCollection<BomGroup> BomGroups { get; set; }

    /// <summary>
    /// BomItem database context.
    /// </summary>
    IMongoCollection<BomItem> BomItems { get; set; }

    /// <summary>
    /// Event database context.
    /// </summary>
    IMongoCollection<Event> Events { get; set; }

    /// <summary>
    /// EventAttribute database context.
    /// </summary>
    IMongoCollection<EventAttribute> EventAttributes { get; set; }

    /// <summary>
    /// EventType database context.
    /// </summary>
    IMongoCollection<EventType> EventTypes { get; set; }

    /// <summary>
    /// Job database context.
    /// </summary>
    IMongoCollection<Job> Jobs { get; set; }

    /// <summary>
    /// Product database context.
    /// </summary>
    IMongoCollection<Product> Products { get; set; }

    /// <summary>
    /// QualityEvent database context.
    /// </summary>
    IMongoCollection<QualityEvent> QualityEvents { get; set; }

    /// <summary>
    /// QualityTest database context.
    /// </summary>
    IMongoCollection<QualityTest> QualityTests { get; set; }

    /// <summary>
    /// QualityVariable database context.
    /// </summary>
    IMongoCollection<QualityVariable> QualityVariables { get; set; }

    /// <summary>
    /// Status database context.
    /// </summary>
    IMongoCollection<Status> Statuses { get; set; }

    /// <summary>
    /// StatusGroup database context.
    /// </summary>
    IMongoCollection<StatusGroup> StatusGroups { get; set; }

    /// <summary>
    /// User database context.
    /// </summary>
    IMongoCollection<User> Users { get; set; }

    /// <summary>
    /// WorkArea database context.
    /// </summary>
    IMongoCollection<WorkArea> WorkAreas { get; set; }

    /// <summary>
    /// WorkCell database context.
    /// </summary>
    IMongoCollection<WorkCell> WorkCells { get; set; }

    /// <summary>
    /// Workorder database context.
    /// </summary>
    IMongoCollection<Workorder> Workorders { get; set; }
}

在 DeleteOneAsync 上更改输入类型时出现新错误 It.IsAny&lt;FilterDefinition&lt;Workorder&gt;&gt;()

Test Name:  MESAPITests.RepositoryTests.WorkorderRepositoryTests.DeleteWorkorderById_ReturnsBooleanTrue
Test FullName:  MESAPITests.RepositoryTests.WorkorderRepositoryTests.DeleteWorkorderById_ReturnsBooleanTrue
Test Source: C:\Users\Zacke\Documents\Repositories\MES-API\MES-API     Tests\RepositoryTests\WorkorderRepositoryTests.cs : line 21
Test Outcome:   Failed
Test Duration:  0:00:00.283

Result StackTrace:  
at MESAPI.Repositories.WorkorderRepository.<DeleteWorkorderById>d__2.MoveNext() in C:\Users\Zacke\Documents\Repositories\MES-API\MES-API\Repositories\WorkRepositories\WorkorderRepository.cs:line 28
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at MESAPITests.RepositoryTests.WorkorderRepositoryTests.<DeleteWorkorderById_ReturnsBooleanTrue>d__1.MoveNext() in C:\Users\Zacke\Documents\Repositories\MES-API\MES-API Tests\RepositoryTests\WorkorderRepositoryTests.cs:line 33
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
Result Message: System.NullReferenceException : Object reference not set to an instance of an object.

Picture: Hovering over result in the line above tells me it's null for some reason..

【问题讨论】:

  • 这里的问题可能是您正在使用的DeleteOneAsync 的重载(即Expression&lt;Func&lt;Workorder, bool&gt;&gt; 是一种静态扩展方法,即实际上根本不在IMongoCollection 接口上,所以设置是'未调用。您可能需要使用DeleteOneAsync like this 的接口重载,或者如果需要对其进行单元测试,则将Mongo 包装在您自己的接口抽象中。您还应该将void DeleteWorkorderById_ReturnsBooleanTrue 更改为返回@987654336 @
  • @StuartLC :我之前使用了“FilterDefinition”而不是上面示例中的表达式,但这给了我同样的错误。关于返回类型的真实情况会改变这一点。
  • @ZackariasMontell 您还将阻塞调用 .ResultWorkorderRepository 中的异步等待混合在一起,这可能导致死锁。
  • @StuartLC,我的错,它实际上导致了另一个类似的堆栈跟踪。我已经在我的问题末尾添加了它。
  • @Nkosi 在测试它是否在正常使用中正常工作时会导致死锁,即通过 swagger 或邮递员向使用此存储库的控制器发出 Http 请求?

标签: c# mongodb unit-testing asp.net-core moq


【解决方案1】:

基于 cmets 的更新。 感谢 StuartLC

Moq 无法模拟扩展方法,应直接在被模拟的接口成员上完成。

IMongoCollection&lt;TDocument&gt;.DeleteOneAsync Method (FilterDefinition&lt;TDocument&gt;, CancellationToken) Method

mockCollection
    .Setup(_ => _.DeleteOneAsync(It.IsAny<FilterDefinition<Workorder>>(), It.IsAny<CancellationToken>()))
    .ReturnsAsync(new DeleteResult.Acknowledged(1));

【讨论】:

  • 在添加您写的第一行之前,我已经尝试过了。就错误而言,它会产生相同的结果。
  • 作为对您的编辑的评论。这不是我已经做过的吗?我将 CancellationToken 从 default(CancellationToken) 更改为 It.IsAny&lt;CancellationToken&gt;() 但这根本不会改变我的结果:/。也许我不完全明白你在说什么..
猜你喜欢
  • 2013-08-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多