6

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?

Soner Gönül
  • 97,193
  • 102
  • 206
  • 364
Chi Chan
  • 11,890
  • 9
  • 34
  • 52

6 Answers6

7

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));
    }
}
Robbie
  • 161
  • 2
  • 4
6

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.

Ladislav Mrnka
  • 360,892
  • 59
  • 660
  • 670
  • Thank you. I didn't know TypeMock and JustMocks intercepts the CLR. That's cool :). They are both commercial so I will see if I can refactor instead. – Chi Chan Feb 20 '13 at 15:38
2

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.

lehn0058
  • 19,977
  • 15
  • 69
  • 109
2

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.

lehn0058
  • 19,977
  • 15
  • 69
  • 109
0

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.

Onosa
  • 1,275
  • 1
  • 12
  • 28
0

Use System.Data.Entity.DbFunctionAttribute and create your "stubbed" implementation of EF DbFunction. Then when you'll run your app it will use EF implementation and when you'll run unit tests then your "stubbed" implementation comes into play.

"Stubbed" implementation which is closest to what Str() from MSSQL does with the use of new System.Data.Entity.DbFunctionAttribute. For those of you guys, who don't like to waste time on reinventing the wheel but needs it for sake of unit tests. Enjoy.

    [DbFunction("SqlServer", "STR")]
    public static string StringConvert(double? number, int? length, int? decimalArg)
    {
        if (number == null)
            return null;

        var roundedValue = decimalArg != null 
                ? Math.Round(number.Value, decimalArg.Value).ToString($"##.{new string('0', decimalArg.Value)}") 
                : number.Value.ToString(CultureInfo.InvariantCulture); 
        
        return length != null && length - roundedValue.Length > 0
            ? $"{roundedValue}{new string(' ', length.Value - roundedValue.Length)}"
            : roundedValue;
    }
Kamil Stadryniak
  • 600
  • 9
  • 25