xUnit InMemoryDatabase Сбой теста: InvalidOperationException - PullRequest
0 голосов
/ 12 ноября 2018

Я запускаю модульные тесты на TFS в среде CI, сегодня произошел сбой со следующим исключением:

Error:
  System.InvalidOperationException : Operations that change non-concurrent collections must have exclusive access. A concurrent update was performed on this collection and corrupted its state. The collection's state is no longer correct.
Stacktrace:
    at System.Collections.Generic.Dictionary`2.FindEntry(TKey key)
    at System.Collections.Generic.Dictionary`2.TryGetValue(TKey key, TValue& value)
    at Microsoft.EntityFrameworkCore.Metadata.Internal.Model.GetDisplayName(Type type)
    at Microsoft.EntityFrameworkCore.Metadata.Internal.Model.FindEntityType(Type type)
    at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.EntityEqualityRewritingExpressionVisitor.RewriteEntityEquality(ExpressionType nodeType, Expression left, Expression right)
    at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.EntityEqualityRewritingExpressionVisitor.VisitBinary(BinaryExpression binaryExpression)
    at System.Linq.Expressions.BinaryExpression.Accept(ExpressionVisitor visitor)
    at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
    at System.Linq.Expressions.ExpressionVisitor.VisitBinary(BinaryExpression node)
    at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.EntityEqualityRewritingExpressionVisitor.VisitBinary(BinaryExpression binaryExpression)
    at System.Linq.Expressions.BinaryExpression.Accept(ExpressionVisitor visitor)
    at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
    at Remotion.Linq.Clauses.WhereClause.TransformExpressions(Func`2 transformation)
    at Remotion.Linq.QueryModel.TransformExpressions(Func`2 transformation)
    at Microsoft.EntityFrameworkCore.Query.Internal.QueryOptimizer.Optimize(QueryCompilationContext queryCompilationContext, QueryModel queryModel)
    at Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor.OptimizeQueryModel(QueryModel queryModel, Boolean asyncQuery)
    at Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor.CreateQueryExecutor[TResult](QueryModel queryModel)
    at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](Expression query, IQueryModelGenerator queryModelGenerator, IDatabase database, IDiagnosticsLogger`1 logger, Type contextType)
    at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass13_0`1.<Execute>b__0()
    at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQueryCore[TFunc](Object cacheKey, Func`1 compiler)
    at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query)
    at System.Linq.Queryable.Any[TSource](IQueryable`1 source)
    at ModelNamespace.SomeRepository.TestMethod()
    at TestNamespace.SomeRepositoryTest.TestMethod_Test()

После этого я снова запустил ту же сборку с той же фиксацией, и она прошла успешно.Странно, не правда ли?

Как создаются тесты

Для настройки InMemoryDatabase мы используем следующий вспомогательный класс:

public class DbContextTestHelper
{
    public static DbContextOptions<CustomContext> PrepareData(Action<CustomContext> createData)
    {
        var options = CreateDbContextOptions();
        SaveData(options, createData);
        return options;
    }

    private static void SaveData(DbContextOptions<CustomContext> options, Action<CustomContext> createData)
    {
        // Insert seed data into the database using one instance of the context
        using (var context = new CustomContext(options))
        {
            createData.Invoke(context);
            context.SaveChanges();
        }
    }

    private static DbContextOptions<CustomContext> CreateDbContextOptions()
    {
        return new DbContextOptionsBuilder<CustomContext>()
            .UseInMemoryDatabase(Guid.NewGuid()
                .ToString()) //Database with same name gets reused, so let's isolate the tests from each other...
            .Options;
    }
}

Так что теперь у нас есть многоТесты, которые мы создаем так:

public class SomeRepositoryTest
{
    [Fact]
    public async Task TestMethod_Test()
    {
        var options = DbContextTestHelper.PrepareData(context =>
        {
            // do some initialisation
        });
        // Use a clean instance of the context to run the test
        using (var context = new CustomContext(options))
        {
            var testee = new SomeRepository(context);
            await testee.TestMethod(); // InvalidOperationException from above
            // Assert something
        }
    }
}

Конечно, во многих случаях мы создаем эти DbContextOptions<CustomContext>, и, естественно, они работают параллельно из-за встроенной функциональности, которую обеспечивает xUnit (да, конечноне в том же классе, но для многих тестовых классов).

Мой вопрос

Насколько я могу судить, существует несколько возможностей того, почему это может иногда вызывать исключение:

  • GUID, сгенерированный для обеспечения разных экземпляровБазы данных InMemory являются дубликатами.Однако я бы предпочел не верить этому.
  • в системе баз данных InMememory есть ошибка
  • Мы неправильно используем базы данных InMemory

Знаете ли вы, что происходит?

Редактировать 1:

По запросу, вот TestMethod(), где вызывается System.Linq.Queryable.Any[TSource](IQueryable'1 source).

    public void TestMethod(SomeObject someObject, List<DateTime> dates, bool someOption)
    {
        var res = _dbContext.SomeSet.Where(o =>
            o.SomeObject.Equals(someObject) && dates.Contains(o.DateTime) && o.SomeOption == someOption);
        if (res.Any()) // at System.Linq.Queryable.Any[TSource](IQueryable`1 source) 
        {
            _dbContext.SomeSet.RemoveRange(res);
        }
    }
...