3

I am unit testing a method in one of my controllers to check that correct data is being returned with a Linq query (this may be an integration test, but the question still stands).

I am trying to follow TTD as much as possible by replicating any errors I get at run time in a test before fixing it in the code.

However, I am running into an error that only occurs during run-time execution of my application not during my test.

The exception I get during run-time is:

Exception thrown: 'System.NotSupportedException' in Newtonsoft.Json.dll

Additional information: LINQ to Entities does not recognize the method 'System.String ToLowerInvariant()' method, and this method cannot be translated into a store expression.

The method under test is:

[HttpGet]
public ActionResult GetRegions(string term)
{
    var regions = from region in DbContext.Culture
                  where
                      !region.NeutralCultureFlag &&
                      (term == null || (region.NativeName + region.CultureCode).ToLowerInvariant().Contains(term.ToLowerInvariant()))
                  orderby region.Name
                  select region;

    return Json(regions.Select(x => new
                {
                    id = x.CultureId,
                    text = x.NativeName + " (" + x.CultureCode + ")"
                })
    );
}

The fix for the error is clearly to change (region.NativeName + region.CultureCode).ToLowerInvariant() to (region.NativeName.ToLowerInvariant() + region.CultureCode.ToLowerInvariant()) but I would like to get this error in my test before I fix it.

My test method is:

public void Test1(string term, int expectedCount)
{
    var result = _controller.GetRegions(term);

    Assert.IsType(typeof (JsonNetResult), result);
    var jsonResult = (JsonNetResult)result;

    var data = GetJsonObject<List<JsonData>>(jsonResult);
}

JsonNetResult is a custom class that extends the normal JsonResult so I can use Json.Net instead of the standard Json serialiser.

The ExecuteResult method in JsonNetResult looks like:

public override void ExecuteResult(ControllerContext context)
{
    if (context == null)
        throw new ArgumentNullException("context");

    HttpResponseBase response = context.HttpContext.Response;
    response.ContentType = string.IsNullOrEmpty(this.ContentType) ? "application/json" : this.ContentType;

    if (this.ContentEncoding != null)
        response.ContentEncoding = this.ContentEncoding;
    if (this.Data == null)
        return;

    var scriptSerializer = JsonSerializer.Create(this.Settings);

    using (var sw = new StringWriter())
    {
        scriptSerializer.Serialize(sw, this.Data); //error occurs here
        response.Write(sw.ToString());
    }
}

The exception occurs at scriptSerializer.Serialize(sw, this.Data);.

Is there a way to replicate the error in my unit test so I can be sure it is fixed properly following TTD principles?

Community
  • 1
  • 1
Steve
  • 9,335
  • 10
  • 49
  • 81
  • Is your test connecting to the same type of database as your production? – Rob Dec 07 '15 at 02:27
  • My test is setup to use a mocked `DbContext`. – Steve Dec 07 '15 at 02:31
  • In that case, the only way to *properly* test it is to have a non-mocked context that connects to a test database - as the error is specifically found in the `LINQ` -> `SQL statement` generator – Rob Dec 07 '15 at 02:34

1 Answers1

1

Because your unit test uses a mocked context, you get LINQ to objects instead of LINQ to entities.

linq to entities vs linq to objects - are they the same?

From above link:

LINQ-to-Objects is a set of extension methods on IEnumerable that allow you to perform in-memory query operations on arbitrary sequences of objects. The methods accept simple delegates when necessary.

LINQ-to-Entities is a LINQ provider that has a set of extension methods on IQueryable. The methods build up an expression tree (which is why delegates are actually passed as Expression<>s), and the provider will build up a SQL query based on its parsing of that expression tree.

As an example, consider the following queries:

 var query1 = mydb.MyEntity.Select(x => x.SomeProp).Where(x => x ==> "Prop");
 var query2 = mydb.MyEntity.Select(x =>> x.SomeProp).AsEnumerable().Where(x => x == "Prop"); 

The first query is will build up an expression tree consisting of a select and a where, with the two lambdas actually considered as LambdaExpressions. The LINQ-to-Entities provider will translate that into SQL that both selects and filters.

The second query inserts an AsEnumerable(), which will force the remainder of the query to use LINQ-to-Objects. In that case, the provider will generate SQL based on only the selection, return all those records from the database, and then the filtering will occur in-memory. Obviously, that's likely going to be much slower.

Community
  • 1
  • 1
jkerak
  • 287
  • 3
  • 13
  • While link only answers are generally frowned upon, I will mark this an accepted because it is correct. For future visitors, to reproduce the error would involve an *integration* test with a real database (that could be in-memory). – Steve Dec 10 '15 at 00:23
  • Yes, in case the link goes dead or the source changes. – Steve Dec 10 '15 at 23:54