3

I am writing to test FromSql Statement with InMemory Database. We are attempting to utilize Sqlite.

Running the following Sql passes the unit test without error.

select * from dbo.Product

However, doing this also passes with incorrect sql syntax. Would like to make the test fail with improper sql syntax. How can we test FromSql properly?

No error came from result of bad syntax .

seledg24g5ct * frofhm dbo.Product

Full Code:

namespace Tests.Services
{
    public class ProductTest
    {
        private const string InMemoryConnectionString = "DataSource=:memory:";
        private SqliteConnection _connection;
        protected TestContext testContext;

        public ProductServiceTest()
        {
            _connection = new SqliteConnection(InMemoryConnectionString);
            _connection.Open();
            var options = new DbContextOptionsBuilder<TestContext>()
                    .UseSqlite(_connection)
                    .Options;
            testContext= new TestContext(options);
            testContext.Database.EnsureCreated();
        }


        [Fact]
        public async Task GetProductByIdShouldReturnResult()
        {
            var productList = testContext.Product
    .FromSql($"seledg24g5ct * frofhm dbo.Product");

            Assert.Equal(1, 1);
        }

Using Net Core 3.1

10
  • Assert.Equal(1, 1); - will always pass ;) Commented Jun 20, 2020 at 7:19
  • yeah, how do I make sure the syntax will fail? Usually it will error out before it reaches that assert ? Commented Jun 20, 2020 at 7:20
  • Would like to make the test fail with improper sql syntax - run tests against actual sql database. Commented Jun 20, 2020 at 7:20
  • I am trying to prevent localhost, since this is an inmemory test, does sqlite not enforce that? github.com/dotnet/efcore/issues/7212 Commented Jun 20, 2020 at 7:22
  • "In-memory" provider even in Sqlite is not an actual sql engine - if you want to test raw sql query run it against actual sql engine. Commented Jun 20, 2020 at 7:28

2 Answers 2

5
+50

There are two things to be taken into consideration here.

First, FromSql method is just a tiny bridge for using raw SQL queries in EF Core. No any validation/parsing of the passed SQL string occurs when the method is called except finding the parameter placeholders and associating db parameters with them. In order to get validated, it has to be executed.

Second, in order to support query composition over the FromSql result set, the method returns IQueryable<T>. Which means it is not executed immediately, but only if/when the result is enumerated. Which could happen when you use foreach loop over it, or call methods like ToList, ToArray or EF Core specific Load extension method, which is similar to ToList, but without creating list - the equivalent of foreach loop w/o body, e.g.

foreach (var _ in query) { }

With that being said, the code snippet

var productList = testContext.Product
    .FromSql($"seledg24g5ct * frofhm dbo.Product");

does basically nothing, hence does not produce exception for invalid SQL. You must execute it using one of the aforementioned methods, e.g.

productList.Load();

or

var productList = testContext.Product
    .FromSql($"seledg24g5ct * frofhm dbo.Product")
    .ToList();

and assert the expected exception.

For more info, see Raw SQL Queries and How Queries Work sections of EF Core documentation.

Sign up to request clarification or add additional context in comments.

Comments

0

@ivan-stoev has answered your question as to why your '.FromSql' statement does nothing - i.e. the query is never actually materialized. But to try and add some additional value, i'll share my Unit Test setup as it works well for me. Of course, YMMV.

  1. Create a reusable class to handle generic In-memory database creation and easy population of tables with test data. NB: this requires the Nuget packages:
  • ServiceStack.OrmLite.Core
  • ServiceStack.OrmLite.Sqlite

I am using OrmLite as it allows for mocking and unit testing by providing a non-disposing connection factory which I can neatly inject into the Test classes via Dependency Injection:

/// <summary>
    /// It is not possible to directly mock the Dapper commands i'm using to query the underlying database. There is a Nuget package called Moq.Dapper, but this approach doesnt need it.
    /// It is not possible to mock In-Memory properties of a .NET Core DbContext such as the IDbConnection - i.e. the bit we actually want for Dapper queries.
    /// for this reason, we need to use a different In-Memory database and load entities into it to query. Approach as per: https://mikhail.io/2016/02/unit-testing-dapper-repositories/
    /// </summary>
    public class TestInMemoryDatabase
    {
        private readonly OrmLiteConnectionFactory dbFactory =
            new OrmLiteConnectionFactory(":memory:", SqliteDialect.Provider);

        public IDbConnection OpenConnection() => this.dbFactory.OpenDbConnection();

        public void Insert<T>(IEnumerable<T> items)
        {
            using (var db = this.OpenConnection())
            {
                db.CreateTableIfNotExist<T>();
                foreach (var item in items)
                {
                    db.Insert(item);
                }
            }
        }
    }
  1. A 'DbConnectionManager<EFContext>' class to provide the wrapper to the database connection using the EF Context you will already have created. This grabs the database connection from the EF Context and abstracts away the opening/closing operations:
public class DbConnectionManager<TContext> : IDbConnectionManager<TContext>, IDisposable
            where TContext : DbContext
        {
            private TContext _context;
    
            public DbConnectionManager(TContext context)
            {
                _context = context;
            }
    
            public async Task<IDbConnection> GetDbConnectionFromContextAsync()
            {
                var dbConnection = _context.Database.GetDbConnection();
    
                if (dbConnection.State.Equals(ConnectionState.Closed))
                {
                    await dbConnection.OpenAsync();
                }
    
                return dbConnection;
            }
    
            public void Dispose()
            {
                var dbConnection = _context.Database.GetDbConnection();
    
                if (dbConnection.State.Equals(ConnectionState.Open))
                {
                    dbConnection.Close();
                }
            }
        } 

Accompanying injectable Interface for the above:

public interface IDbConnectionManager<TContext>
        where TContext : DbContext
    {
        Task<IDbConnection> GetDbConnectionFromContextAsync();

        void Dispose();
    }
  1. In your .NET Project Startup class, register this interface with the inbuilt DI container (or whatever one you're using):
public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped(typeof(IDbConnectionManager<>), typeof(DbConnectionManager<>));
}
  1. Now our Unit Test class looks like this:
/// <summary>
    /// All tests to follow the naming convention: MethodName_StateUnderTest_ExpectedBehaviour
    /// </summary>
    [ExcludeFromCodeCoverage]
    public class ProductTests
    {
        //private static Mock<ILoggerAdapter<Db2DbViewAccess>> _logger;
        //private static Mock<IOptions<AppSettings>> _configuration;
        private readonly Mock<IDbConnectionManager<Db2Context>> _dbConnection;

        private readonly List<Product> _listProducts = new List<Product>
        {
            new Product
            {
                Id = 1,
                Name = "Product1"
            },
            new Product
            {
                Id = 2,
                Name = "Product2"
            },
            new Product
            {
                Id = 3,
                Name = "Product3"
            },
        };

        public ProductTests()
        {
            //_logger = new Mock<ILoggerAdapter<Db2DbViewAccess>>();
            //_configuration = new Mock<IOptions<AppSettings>>();
            _dbConnection = new Mock<IDbConnectionManager<Db2Context>>();
        }

        [Fact]
        public async Task GetProductAsync_ResultsFound_ReturnListOfAllProducts()
        {
            // Arrange
            // Using a SQL Lite in-memory database to test the DbContext. 
            var testInMemoryDatabase = new TestInMemoryDatabase();
            testInMemoryDatabase.Insert(_listProducts);

            _dbConnection.Setup(c => c.GetDbConnectionFromContextAsync())
                .ReturnsAsync(testInMemoryDatabase.OpenConnection());

            //_configuration.Setup(x => x.Value).Returns(appSettings);

            var productAccess = new ProductAccess(_configuration.Object); //, _logger.Object, _dbConnection.Object);

            // Act
            var result = await productAccess.GetProductAsync("SELECT * FROM Product");

            // Assert
            result.Count.Should().Equals(_listProducts.Count);
        }
    }

Notes on the above:

  • You can see i'm testing a 'ProductAccess' Data Access class which wraps my database calls but that should be easy enough to change for your setup. My ProductAccess class is expecting other services such as logging and Configuration to be injected in, but i have commented these out for this minimal example.
  • Note the setup of the in-memory database and populating it with your test list of entities to query is now a simple 2 lines (you could even do this just once in the Test class constructor if you want the same test dataset to use across tests):

var testInMemoryDatabase = new TestInMemoryDatabase(); testInMemoryDatabase.Insert(_listProducts);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.