How to mock LINQ to Entities helpers such as '

2019-06-25 05:59发布

问题:

I am using EF 4 and is trying to unit test the follow line using Moq:

var convertError = models
             .Where(x => SqlFunctions.StringConvert((decimal?) (x.convert ?? 0)) == "0")
             .Any();

and it seems like SqlFunctions.StringConvert() will throw if it detects the context is mocked.

It gives an error saying:

This function can only be invoked from LINQ to Entities

Is it possible to tell SqlFunctions.StringConvert to return a mock object so I can get rid of this error?

回答1:

No it is not possible because the function's implementation looks like:

[EdmFunction("SqlServer", "STR")]
public static string StringConvert(decimal? number, int? length)
{
    throw EntityUtil.NotSupported(Strings.ELinq_EdmFunctionDirectCall);
}

You cannot use Moq to fake this function. You need more powerful mocking framework which will allow you replacing static function call - probably Microsoft Fakes, TypeMock Isolator or JustMock.

Or you need to think about your testing approach because mocking the context is the wrong idea. You should instead have something like:

var convertError = myQueryProvider.ConvertQuery(x.convert); 

Where queryProvider will be your mockable type hiding your query. Query is database related logic and it should be tested against the real database. Code around your query is your application logic and it should be unit tested - the best solution to test them both correctly is simply to separate them through some interface (query provider in this case but people often go with a full specific repository). This principle comes from separation of concerns - query execution is separate concern so it is placed into its own method which is tested separately.



回答2:

What I did was to provide my own implementations of DbFunctions such that LINQ To Objects in a unit test uses a simple .NET implementation and LINQ To EF at runtime uses the DbFunctionAttribute in the same way System.Data.Entity.DbFunctions would. I had thought about mocking DbFunctions but hey, the LINQ to Objects implementations are useful and work fine. Here is an example:

public static class DbFunctions
{
    [DbFunction("Edm", "AddMinutes")]
    public static TimeSpan? AddMinutes(TimeSpan? timeValue, int? addValue)
    {
        return timeValue == null ? (TimeSpan?)null : timeValue.Value.Add(new TimeSpan(0, addValue.Value, 0));
    }
}


回答3:

You are able to mock EdmFunctions, and I have done this using NSubstitute (which also doesn't support mocking static functions). The trick is to wrap your DbContext in an interface. Then, add your static EdmFunction function to a static class and create an extension method to your context in the static class to call the method. For example

public static class EdmxExtensions
{
   [EdmFunction("SqlServer", "STR")]
   public static string StringConvert(decimal? number, int? length)
   {
      throw EntityUtil.NotSupported(Strings.ELinq_EdmFunctionDirectCall);
   }

   public static IQueryable<Person> MyFunction(this IDbContext context, decimal? number, int? length)
   {
      context.Person.Where(s => StringConvert(s.personId, number, length);
   }

You will then be able to mock MyFunction since it is a method available to an interface, and EntityFramework doesn't get angry when you try to call it.

I have not tried this with Moq, but you may be able to do this in a similar way.



回答4:

Another approach you could write your own method that has the same attribute tags and method signature and then actually implement the method unit test purposes instead of throwing an exception. Entity Framework ignores the code in the function, so it will never call it.



回答5:

You cannot tell SqlFunctions.StringConvert to return a mock object as it is a static method. But you can make an interface for it and create a facade class.

Create an interface like so and be sure to include the attribute

public interface ISqlFunctions
{
    [System.Data.Entity.Core.Objects.DataClasses.EdmFunction("SqlServer", "STR")]
    string StringConvert(Decimal? number);
}

Then write your facade class. This should be the C# way of doing whatever you want Linq to Entity to do.

public class SqlFunctionsFacade : ISqlFunctions
{
    public string StringConvert(decimal? number)
    {
        return number?.ToString();
    }
}

In your implementation, use your interface in your linq query

    public SomethingOrOther(ISqlFunctions sqlFunctions)
    {
        var convertError = models
            .Where(x => sqlFunctions.StringConvert((decimal?)(x.convert ?? 0)) == "0")
            .Any();
    }

Entity Framework will use the attribute on the interface in the same way it was used on SqlFunctions.StringConvert(decimal?).

In your unit test, you can supply your system under test with your facade class or a mock of the interface.