Andrew Matthews

Hi,

How easy will it be to get Mock query objects using LINQ i.e. it might be nice to intercept calls to prevent LINQ to SQL from incurring the cost of DB calls during a unit test.

Andrew Matthews



Re: LINQ Project General Mockable LINQ

TikiWan

Are you looking for something like this

Northwind db = new Northwind(Constr);

db.Log = console.Out;

var q = from x in db.Customers

where x.City == "London"

select x;

var commands = db.GetCommand(q);

Hope this help.





Re: LINQ Project General Mockable LINQ

David Buchanan

Here's a stab at Mocked LINQ:

I've got a SandboxDataContext, which was created from four tables in Northwind including the Category table.

I extracted Interface and produced an ISandboxDataContext... fortunately, it's a pretty simple interface. I modified it to be simpler (IEnumerable<T> instead of Linq.Table<T>), but there is still a problem.

public interface ISandboxDataContext

{

IEnumerable<Category> Categories { get; }

IEnumerable<Order_Detail> Order_Details { get; }

IEnumerable<Product> Products { get; }

IEnumerable<Supplier> Suppliers { get; }

}

Then I implemented the interface:

class MockSandboxDataContext : ISandboxDataContext

{

private List<Category> _MyCatTable;

public MockSandboxDataContext()

{

_MyCatTable = new List<Category>();

Category MyCat = new Category();

//can't do this, it's read only

//MyCat.CategoryID = 1;

MyCat.CategoryName = "Mock";

MyCat.Description = "Good";

_MyCatTable.Add(MyCat);

}

#region ISandboxDataContext Members

IEnumerable<Category> ISandboxDataContext.Categories

{

get

{

return _MyCatTable;

}

}

...

So far so good, how about a test method

public static void UseISandbox(ISandboxDataContext MyDB)

{

List<Category> MyList = new List<Category>();

var GetARecord = (from x in MyDB.Categories select x).Take(1);

Console.WriteLine(GetARecord.AsQueryable().ToString());

MyList = GetARecord.ToList();

Console.WriteLine(MyList.Count);

foreach (var s in MyList)

Console.WriteLine("{0} {1}", s.CategoryID, s.CategoryName);

}

And a couple of test calls:

CRUD.UseISandbox(new MockSandboxDataContext());

CRUD.UseISandbox(new SandboxDataContext());

And output:

System.Linq.Enumerable+<TakeIterator>d__2c`1[DDBSandboxApp.Category]
1
0 Mock
System.Linq.Enumerable+<TakeIterator>d__2c`1[DDBSandboxApp.Category]
1
1 Beverages

Great!

Now for the problems... as you can see in the output, it used the IEnumerable technique to resolve the query. A Trace on the database reveals that, when using the ISandboxDataContext, a bad query is used:

SELECT [t0].[CategoryID], [t0].[CategoryName], [t0].[Description], [t0].[Picture]
FROM [dbo].[Categories] AS [t0]

This is because the interface uses IEnumerable<T> instead of IQueryable<T>. I used IEnumerable<T> because List<T> doesn't implement IQueryable<T>. I need to find another collection object to use instead of List<T>.

Another problem is the readonly-ness of the IDENTITY field in the Category object.





Re: LINQ Project General Mockable LINQ

David Buchanan

Using List<>.AsQueryable<T> to change my list up to a queryable, and making the appropriate type changes in the interface...

System.Collections.Generic.List`1[DDBSandboxApp.Category].Select(x => x).Take(1)

1
0 Mock
SELECT TOP 1 [t0].[CategoryID], [t0].[CategoryName], [t0].[Description], [t0].[P
icture]
FROM [dbo].[Categories] AS [t0]
1
1 Beverages

Muahaha!

Hmm,

public partial class Category

I can extend Category to allow the ID to be set... (and I can control when that extension is allowed to exist).

I think that's it. There is the capability to do UnitTests that demonstrate how a linQuery reads data determined by the UnitTest Writer without hits to the database.





Re: LINQ Project General Mockable LINQ

Matt Warren - MSFT

Use IQueryable<T> instead of IEnumerable<T>. For your sandbox convert the lists into IQueraybles with list.AsQueryable().






Re: LINQ Project General Mockable LINQ

Andrew Matthews

Hi David,

I also created a little mock query objects as well, before seeing your reply.

Here it is - it seems to do pretty much the same as yours:

http://aabs.wordpress.com/2007/06/26/using-mock-objects-when-testing-linq-code/

regards,

Andrew





Re: LINQ Project General Mockable LINQ

Andrew Matthews

Hi Matt,

I also used IEnumerable (the untyped version). What advantage would I get out of using IQueryable<T> instead

Andrew





Re: LINQ Project General Mockable LINQ

David Buchanan

Reference | Actual

IEnumerable | InMemoryCollection - LINQ iterates over the collection.

IQueryable | InMemoryCollection - LINQ iterates over the collection.

IEnumerable | DatabaseTable - LINQ loads the table into memory (table scan) and iterates over the collection (bad).

IQueryable | DatabaseTable - LINQ generates SQL and lets the database do the work (probable index seek).





Re: LINQ Project General Mockable LINQ

Ian Cooper - MVP

I cover a lot of this in Being Ignorant with LINQ to SQL




Re: LINQ Project General Mockable LINQ

David Buchanan

That's a very cool article that I have read previously to it being mentioned here.

One thing I noticed about the IntegrationTesting for add/remove commands against some external repository...

The test is to add a customer to a customer repository, and then search the repository for the customer (in an attempt to prove the repository stored the customer).

This test could yield false positives if that customer is already in the repository.





Re: LINQ Project General Mockable LINQ

Ian Cooper - MVP

True. For acceptance/customer tests (that touch the Db unlike a unit test) we tend to work as follows:

Build the Db from a script as part of the build before running the integration test.

Put the code in a using(new TransactionScope) {} block

When the TransactionScope goes out of scope without completing the transaction will abort and the changes will be rolled back

That means that you can be pretty certain that your tests are passing without any false positives.





Re: LINQ Project General Mockable LINQ

WilliamStacey

Perhaps a clean way to do this is using a DataContext as the source instead of a connection string in the ctor - 2 new ctors would need to be added.

ServerDBDataContext dbMock = new ServerDBDataContext(false);

db.Customers.Add(...); // fill context with data.

ServerDBDataContext db = new ServerDBDataContext(dbMock);

var custs = db.Customers;

The first DataContext is the mock. The false ctor says don't use any db, I will fill the context with data myself. The real Context takes the mock Context in the ctor. Now use the db context as normal. To change back to using db, just change "new ServerDBDataContext(dbMock)" to "new ServerDBDataContext()". Seems like that would be pretty clean.





Re: LINQ Project General Mockable LINQ

Chris Brandsma

I dont see a DataContext constructor that accepts a boolean value. Did you create that yourself Or am I missing a setting somewhere






Re: LINQ Project General Mockable LINQ

WilliamStacey

right. It is idea of a new feature.




Re: LINQ Project General Mockable LINQ

Chakkaradeep

Hi Ian,

Ian Cooper - MVP wrote:
I cover a lot of this in Being Ignorant with LINQ to SQL

Nice article and it did cover how to do unit testing with linq-to-sql. But I am stuck with the FakeTable implementation. The RTM release doesnt have ITable interface with generic type and also the CreateQuery function which you have in FakeTable also seems to create problems.

Can you put some sample or update to the RTM version It would be really helpful for beginners like me. I am trying to follow and implement the same, but I am not able to do it.

As of now, I have something

public class FakeTable<TEntity> : ITable

{

private List<TEntity> source;

private IQueryable<TEntity> query;

public List<TEntity> Source

{

get { return source; }

set { source = value; }

}

public IQueryable<TEntity> Query

{

get { return query; }

set { query = value; }

}

public FakeTable(List<TEntity> source)

{

this.source = source;

this.query = source.AsQueryable<TEntity>();

}

#region ITable Members

public void Attach(object entity, object original)

{

throw new System.NotImplementedException();

}

public void Attach(object entity, bool asModified)

{

throw new System.NotImplementedException();

}

public void Attach(object entity)

{

throw new System.NotImplementedException();

}

public void AttachAll(System.Collections.IEnumerable entities, bool asModified)

{

throw new System.NotImplementedException();

}

public void AttachAll(System.Collections.IEnumerable entities)

{

throw new System.NotImplementedException();

}

public DataContext Context

{

get { throw new System.NotImplementedException(); }

}

public void DeleteAllOnSubmit(System.Collections.IEnumerable entities)

{

if (source == null)

throw new System.NullReferenceException("Source not set");

foreach(TEntity entity in entities)

{

source.Remove(entity);

}

}

public void DeleteOnSubmit(object entity)

{

if (source == null)

throw new System.NullReferenceException("Source not set");

source.Remove((TEntity) entity);

}

public ModifiedMemberInfo[] GetModifiedMembers(object entity)

{

throw new System.NotImplementedException();

}

public object GetOriginalEntityState(object entity)

{

throw new System.NotImplementedException();

}

public void InsertAllOnSubmit(System.Collections.IEnumerable entities)

{

if (source == null)

source = new List<TEntity>();

source.AddRange((IEnumerable<TEntity>)entities);

}

public void InsertOnSubmit(object entity)

{

if (source == null)

source = new List<TEntity>();

source.Add((TEntity) entity);

}

public bool IsReadOnly

{

get { throw new System.NotImplementedException(); }

}

#endregion

#region IEnumerable Members

public System.Collections.IEnumerator GetEnumerator()

{

return source.GetEnumerator();

}

#endregion

#region IQueryable Members

public System.Type ElementType

{

get { return typeof (TEntity); }

}

public System.Linq.Expressions.Expression Expression

{

get { return System.Linq.Expressions.Expression.Constant(this); }

}

public IQueryProvider Provider

{

get { return query.Provider; }

}

#endregion

}

But with this, am not able to query against a FakeTable object and able to only query with the List object in the FakeEntity class. So,

this works,

Code Block

var query = (from a in adminTable.Source

where a.AdminId == "admin1"

select a).First().AdminPassword;

but this doesnt,

Code Block

var query = (from a in adminTable

where a.AdminId == "admin1"

select a).First().AdminPassword;

If this works, I can as suggested in the article, I could mock the datacontext and have FakeTable<TEntity> instead of Table<Entity>

Any ideas suggestions

P.S

===

I am a beginner of this TDD and am getting the concepts slowly, so any help is appreciated Smile

Thanks,