2012-05-23 10 views
24

Im za pomocą Moq do tworzenia makiety zestawu danych.Utwórz wyrażenie <Func<,>> używając odbicia

Utworzyłem małą klasę helperów, która pozwala mi przechowywać w pamięci zamiast bazy danych, która sprawia, że ​​testowanie jednostki jest proste. W ten sposób mogę dodawać i usuwać elementy z mojego próbnego zestawu danych, co pozwala mi testować moje wstawianie i usuwanie zgłoszeń serwisowych.

Podczas konfiguracji makiety mam linię, która wygląda następująco

this.Setup(i => i.AcademicCycles).Returns(mockStore.GetList<AcademicCycle>()); 

My mock ma wiele właściwości, więc chciałbym, aby wykonać ten krok konfiguracji przy użyciu odbicia. Udało mi się wykonać część procesu pracując przez odbicie, ale utknąłem na metodzie lambda na Setup.

Setup bierze

Expression<Func<GoalsModelUnitOfWork, IQueryable<AcademicCycle>>> odpowiadający i => i.AcademicCycles

i chciałbym stworzyć ten dynamicznie. Przy użyciu odbicia Mam następujący:

Nazwa nieruchomości: „AcademicCycles”

Typ IQueryable<AcademicCycle>

Typ AcademicCycle

Mam również wystąpienie i w rachunku lambda Który jest

Odpowiedz

23

Kod do dynamicznego utworzenia wyrażenia byłby następujący:

ParameterExpression parameter = Expression.Parameter(typeof (GoalsModelUnitOfWork), "i"); 
MemberExpression property = Expression.Property(parameter, "AcademicCycles"); 

var queryableType = typeof (IQueryable<>).MakeGenericType(typeof (AcademicCycle)); 
var delegateType = typeof (Func<,>).MakeGenericType(typeof (GoalsModelUnitOfWork), queryableType); 

var yourExpression = Expression.Lambda(delegateType, property, parameter); 

Rezultatem będzie miał żądany typ, ale problemem jest to, że typ powrót Expression.Lambda() jest LambdaExpression i nie można wykonać typ obsady do Expression<Func<...>> przekazać go jako parametr do funkcji setup bo don” t znać ogólne parametry typu dla Func. Więc trzeba wywołać metodę Setup przez odbicie też:

this.GetType().GetMethod("Setup", yourExpression.GetType()).Invoke(this, yourExpression); 
+1

Właściwie wynikiem Expression.Lambda mogą być oddane do 'Expression >' jeśli statycznie znają parametr i powrotu typów. Wewnętrzna Expression.Lambda konstruuje instancję odpowiedniego typu 'Expression >', mimo że zwracany typ Expression.Lambda jest słabo wpisany. – itowlson

+1

Też nie potrzebuję środkowych dwóch linii. Od testowania w prostszym przypadku, 'var lambda = Expression.Lambda (parametr, właściwość)' powinien działać (Expression.Lambda opracuje typ delegata z typów i właściwości). Jednak mój kod testowy był nieco inny od twojego i używał prostszych typów, więc twój przebieg może się różnić ...! – itowlson

2

postanowiłem wziąć w nim pęknięcia i skończyło się z tego boga strasznej kawałek kodu.

Nie jestem ekspertem od refleksji i jest to tylko pierwsza próba uzyskania czegoś działającego. Byłbym bardzo zainteresowany tym, jakie inne podejścia mają ludzie, lub czy któreś z bibliotek opakowań relfection może uczynić to ładniejszym.

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Linq.Expressions; 
using System.Reflection; 
using Moq; 
using Xunit; 

namespace MyExample 
{ 
    public class Tests 
    { 
     [Fact] 
     public void Test() 
     { 
      Dictionary<Type, object> data = new Dictionary<Type, object> 
      { 
       { typeof(IQueryable<Cycle>), new List<Cycle> { new Cycle { Name = "Test" } }.AsQueryable() }, 
       { typeof(IQueryable<Rider>), new List<Rider> { new Rider { Name = "1"}, new Rider { Name = "2" } }.AsQueryable() } 
      }; 

      var mock = new Mock<IDataContext>(); 
      var setup = mock.GetType().GetMethods().Single(d => d.Name == "Setup" && d.ContainsGenericParameters); 
      var param = Expression.Parameter(typeof(IDataContext), "i"); 
      foreach (var property in typeof(IDataContext).GetProperties(BindingFlags.Public | BindingFlags.Instance)) 
      { 
       // Build lambda 
       var ex = Expression.Lambda(Expression.MakeMemberAccess(param, property), param); 

       // Get generic version of the Setup method 
       var typedSetup = setup.MakeGenericMethod(property.PropertyType); 

       // Run the Setup method 
       var returnedSetup = typedSetup.Invoke(mock, new[] { ex }); 

       // Get generic version of IReturns interface 
       var iReturns = typedSetup.ReturnType.GetInterfaces().Single(d => d.Name.StartsWith("IReturns`")); 

       // Get the generic Returns method 
       var returns = iReturns.GetMethod("Returns", new Type[] { property.PropertyType }); 

       // Run the returns method passing in our data 
       returns.Invoke(returnedSetup, new[] { data[property.PropertyType] }); 
      } 

      Assert.Equal(1, mock.Object.Cycles.Count()); 
     } 
    } 

    public class Cycle 
    { 
     public string Name { get; set; } 
    } 

    public class Rider 
    { 
     public string Name { get; set; } 
    } 

    public interface IDataContext 
    { 
     IQueryable<Cycle> Cycles { get; set; } 

     IQueryable<Rider> Riders { get; set; } 
    } 
} 
2

Ta metoda powinna skonstruować wyrażenie lambda. Ponieważ wywołujesz metodę Setup przez odbicie, nie potrzebujesz silnie wpisanego wyrażenia lambda; masz zamiar przekazać ją jako część tablicy obiektu podczas rozmowy Invoke:

public LambdaExpression PropertyGetLambda(string parameterName, Type parameterType, string propertyName, Type propertyType) 
    { 
     var parameter = Expression.Parameter(parameterType, parameterName); 
     var memberExpression = Expression.Property(parameter, propertyName); 
     var lambdaExpression = Expression.Lambda(memberExpression, parameter); 
     return lambdaExpression; 
    } 

Nie sądzę rzeczywiście trzeba nazwę parametru.Jeśli mam rację, można uprościć bitowe:

public LambdaExpression PropertyGetLambda(Type parameterType, string propertyName, Type propertyType) 
    { 
     var parameter = Expression.Parameter(parameterType); 
     var memberExpression = Expression.Property(parameter, propertyName); 
     var lambdaExpression = Expression.Lambda(memberExpression, parameter); 
     return lambdaExpression; 
    }