question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Unit testing dependent repository classes—difficulty mocking DataConnection.

See original GitHub issue

I’m not sure if this is a library design issue, or if I’m just taking the wrong approach to testing. I’m new to this library, so go easy on me if I’m just overlooking something stupid.

I’m having a difficult time trying to find a practical way to unit test my repository classes that rely on this library. Ideally, I’d like to test the functionality of my classes themselves, mocking the behavior of the LinqToDb.Data.DataConnection dependency so I can isolate my own code.

However, I’m having a hard time doing that, and I can’t seem to much advice on how to proceed. I did find this blog post by @cskardon from 2012, which is a pretty low-effort way to mock data retrieval in the DataConnection by using a basic IQueryable implementation, but the moment my mocks attempt to call the Insert/Update/etc extension methods, things get weird.

Using the documentation as a guide, I have something along these lines for a data context class:

public class RateDataContext : LinqToDB.Data.DataConnection
{
    public LinqToDBFacetsContext(IDataProvider provider, string connectionString) 
        : base (provider, connectionString) {}

    public virtual ITable<Rate> Rates => GetTable<Rate>();
}

And the following repository that depends on it:

public class LinqToDbRateRepository : IRateRepository
{
    private RateDataContext Context { get; }

    public LinqToDbRateRepository (RateDataContext context)
    {
        Context = context;
    }

    public Rate GetRate(int rateId)
    {
        // ... some logic here about the ID entered
        var rate = Context.Rates.SingleOrDefault(r => r.Key == rateId);
        // With the MockTable, my tests get through this just fine.
        // ... some transformation logic here
        return rate;
    }

    public int CreateRate(Rate rate)
    {
        // ... some validation and transformation logic here.
        // My attempts to mock (MockTable, Moq, etc) blow up with this extension method call:
        var result = db.InsertWithIdentity(rate);
        int newId = Convert.ToInt32(result);
        // ... any internal tracking that needs to be done with the new key.
        return newId;
    }
}

I’ve tried using Moq for the data context class, but since the .InsertWithIdentity, .Insert, .Update methods are all extension methods, I can’t fake those as part of my mocks. Unless I’m missing something, it seems like I’d have to write a fair number of custom implementations to provide all the things those methods depend on in order to mimic the behavior.

Is there a better way to approach designing my classes to more effectively isolate my code for unit testing? Or is this library just not a good candidate for unit testing? What practices have you seen in the wild?

Issue Analytics

  • State:closed
  • Created 7 years ago
  • Comments:9 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
MaceWinducommented, Nov 24, 2018

My opinion - database code should be tested against database, because mock will just hide issues you have in query.

If you need to test database-agnostic logic - maybe you should separate your code to do something one and inject database-specific part as mockable dependency.

Actually several years ago I had similar idea to write tests with mocking linq2db using IEnumerable data, but I only implemented linq2db abstraction and never really mocked it in tests (still it helped with tests a lot).

It contains:

  1. interface IQuerableExtensions that duplicates linq2db extensions (only those I actually use):
public interface IQuerableExtensions
{
        int Delete<TEntity>(IQueryable<TEntity> source);

        int Delete<TEntity>(IQueryable<TEntity> source, Expression<Func<TEntity, bool>> predicate);

        IQueryable<TEntity> Having<TEntity>(IQueryable<TEntity> source, Expression<Func<TEntity, bool>> predicate);

        IQueryable<TEntity> FullJoin<TEntity>(IQueryable<TEntity> source, Expression<Func<TEntity, bool>> predicate);

        int Insert<TEntity>(ITable<TEntity> target, Expression<Func<TEntity>> setter);

        int InsertOrUpdate<TEntity>(
            ITable<TEntity> target,
            Expression<Func<TEntity>> insertSetter,
            Expression<Func<TEntity, TEntity>> onDuplicateKeyUpdateSetter);

        IQueryUpdatable<TEntity> Set<TEntity, TValue>(
            IQueryUpdatable<TEntity> source,
            Expression<Func<TEntity, TValue>> extract,
            TValue value);

        IQueryUpdatable<TEntity> Set<TEntity, TValue>(
            IQueryUpdatable<TEntity> source,
            Expression<Func<TEntity, TValue>> extract,
            Expression<Func<TEntity, TValue>> update);

        IQueryUpdatable<TEntity> Set<TEntity, TValue>(
            IQueryUpdatable<TEntity> source,
            Expression<Func<TEntity, TValue>> extract,
            Expression<Func<TValue>> update);
// and so on
...
  1. all interfaces used by methods above actually are not linq2db interfaces, but something like that:
public interface ITable<T> : IQueryable<T>
{
}

public interface IQueryUpdatable<out T>
{
}

with implementation for linq2db:

internal class Linq2DbTable<TEntity> : ITable<TEntity>
{
        public Linq2DbTable(LinqToDB.ITable<TEntity> linq2dbTable)
        {
            TableImpl = linq2dbTable;
        }

        public LinqToDB.ITable<TEntity> TableImpl { get; }

        Type IQueryable.ElementType => TableImpl.ElementType;

        Expression IQueryable.Expression => ((IQueryable)TableImpl).Expression;

        IQueryProvider IQueryable.Provider => TableImpl.Provider;

        IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)TableImpl).GetEnumerator();

        IEnumerator<TEntity> IEnumerable<TEntity>.GetEnumerator() => TableImpl.GetEnumerator();
}

public class Linq2DbUpdatable<TEntity> : IQueryUpdatable<TEntity>
{
        public Linq2DbUpdatable(IUpdatable<TEntity> linq2dbObject)
        {
            Linq2dbObject = linq2dbObject;
        }

        public IUpdatable<TEntity> Linq2dbObject{ get; }
}
  1. linq2db implementation of extensions interface:
internal class Linq2DbQueryableExtensions : IQuerableExtensions
{
        int IQuerableExtensions.Delete<TSource>(IQueryable<TSource> source)
        {
            return LinqExtensions.Delete(source);
        }

        int IQuerableExtensions.Delete<TSource>(IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)
        {
            return LinqExtensions.Delete(source, predicate);
        }

        IQueryable<TSource> IQuerableExtensions.FullJoin<TSource>(
            IQueryable<TSource> source,
            Expression<Func<TSource, bool>> predicate)
        {
            return LinqExtensions.FullJoin(source, predicate);
        }

        IQueryable<TSource> IQuerableExtensions.Having<TSource>(
            IQueryable<TSource> source,
            Expression<Func<TSource, bool>> predicate)
        {
            return LinqExtensions.Having(source, predicate);
        }
...
  1. Actual extension methods used in code. This is where you inject your test mocks for items 2-3
public static class QuerableExtensions
{
        // this should be injected by test/app setup, but because I nerver used it in tests - it is just hardcoded for me
        private static readonly IQuerableExtensions _extensionsImpl = new Linq2dbQueryableExtensions();

        public static int Delete<TSource>(this IQueryable<TSource> source)
        {
            return _extensionsImpl.Delete(source);
        }

        public static int Delete<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)
        {
            return _extensionsImpl.Delete(source, predicate);
        }

        public static IQueryable<TSource> Having<TSource>(
            this IQueryable<TSource> source,
            Expression<Func<TSource, bool>> predicate)
        {
            return _extensionsImpl.Having(source, predicate);
        }

        public static IQueryable<TSource> FullJoin<TSource>(
            this IQueryable<TSource> source,
            Expression<Func<TSource, bool>> predicate)
        {
            return _extensionsImpl.FullJoin(source, predicate);
        }

Next you need to connect it to your DataConnection/mock provider

  1. I have interface to mock data connection with methods I really use in application
public interface IDbManager : IDisposable
{
        void CommitTransaction();

        ITable<TEntity> GetTable<TEntity>() where TEntity : class;

        int Insert<TEntity>(TEntity obj);
// ... other methods skipped
}
  1. implementation for linq2db (or your mock)
internal class Linq2DbDbManager : IDbManager
{
        ITable<TEntity> IDbManager.GetTable<TEntity>()
        {
            return new Linq2DbTable<TEntity>(GetDbManager().GetTable<TEntity>());
        }

        int IDbManager.Insert<TEntity>(TEntity obj)
        {
            return GetDbManager().Insert(obj);
        }

        private DataConnection GetDbManager()
        {
                // my app-specific code to return instance of linq2db connection
        }
// skipped
}
  1. data connection base class that I use to implement my db connection classes. here you inject your db-connection abstraction (IDbManager)
public abstract class BaseRepository<TRepository> : IDbManager where TRepository : BaseRepository<TRepository>
    {
        protected BaseRepository(IDbManager manager)
        {
            DataConnection = manager;
        }

        internal IDbManager DataConnection { get; }

        public void CommitTransaction()
        {
            DataConnection.CommitTransaction();
        }

        public ITable<TEntity> GetTable<TEntity>() where TEntity : class
        {
            return DataConnection.GetTable<TEntity>();
        }

        public int Insert<TEntity>(TEntity obj)
        {
            return DataConnection.Insert(obj);
        }

In the end I didn’t ever implemented in-memory db mock for this infrastructure, but actually this extra abstraction layer helped me a lot with tests:

  • I was able tune my layer to run tests in parallel with separate test database for each test thread
  • helped me to implement db migrations and use them easily in tests and production
1reaction
gsteinbachercommented, Jun 30, 2016

So far I have found that only the four “transaction” methods (BeginTransaction(), BeginTransaction(IsolationLevel isolationLevel), CommitTransaction() and RollbackTransaction()) are preventing me from mocking the DataConnection class. My suggestion is, if you don’t want to add the transaction methods to the IDataContext interface maybe you can create another interface that contains these four methods (call it ITransaction) and add that interface to the DataConnection class.

This would give us the ability to test our repository classes without requiring us to run your Linq2Db code in our tests AND eliminate the need for an external database.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Unit testing classes with mocked repository dependecies
The problems is obvious - the test depended on the implementation details. I knew that the FindActiveCategories method uses FindAll method of ...
Read more >
In Unit Testing, why would I create a Repository twice?
To test that the dependent class properly reacts to various kinds of errors, you provide a mock version that returns all sorts of...
Read more >
Unit Testing on Top of Entity Framework DbContext
In data intensive applications one of the most common difficulties when writing unit tests is to isolate them from the database.
Read more >
Unit Testing Part — 2 (Database mocking)
The unit test is used to test a small unit of code like method. If that method is using a database, from time...
Read more >
Why I will stop mocking for most of my Unit Tests in a ...
Not a hard and fast rule, but if mocking and setting up unit tests is such a chore, sometimes it's indicative of bad...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found