Effort

From Logic Wiki
Jump to: navigation, search


to install Effort to Entity Framework 6

Install-Package Effort.EF6

Effort

http://effort.codeplex.com/

This project is an Entity Framework provider that runs in memory. You can still use your DbContext or ObjectContext classes within unit tests, without having to have an actual database.

Many of the features of Entity Framework will still work. Navigation Properties will automatically be loaded with related entities and the DbSet.Include method also works as it would if connected to an real database.

A draw back of this approach is that we are still not running against a real database, but instead a different Entity Framework provider. Some operations do not behave as they would against a real database. In fact, at the time of writing, Effort does not support some operations that are supported by other database providers. (See this issue on CodePlex).

Essentially, this approach helps to solve points 2 and 3 above, but not 1.


An Example Entity Framework model

In this example, the Entity Model contains "Product" and "Tag" entities, which have a many-to-many relationship between the them. This relationship is facilitated with a "ProductTag" entity, that relates a Product with a Tag:

 
public partial class Product
{   
    public System.Guid Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public Nullable<decimal> Price { get; set; }
    public System.DateTime CreatedDate { get; set; }
    
    public virtual ICollection<ProductTag> ProductTags { get; set; }
}

public partial class Tag
{
    public System.Guid Id { get; set; }
    public string Name { get; set; }
    public System.DateTime CreatedDate { get; set; }
    
    public virtual ICollection<ProductTag> ProductTags { get; set; }
}

public partial class ProductTag
{
    public System.Guid Id { get; set; }
    public System.Guid ProductId { get; set; }
    public System.Guid TagId { get; set; }
    public System.DateTime CreatedDate { get; set; }
    
    public virtual Product Product { get; set; }
    public virtual Tag Tag { get; set; }
}    

I also have a MyAppContext class that inherits from DbContext.

 
public partial class MyAppContext : DbContext
{
    public MyAppContext()
        : base("name=MyAppContext")
    {
        this.Configuration.LazyLoadingEnabled = false;
    }
    
    public MyAppContext(string connectionString)
        : base(connectionString)
    {
        this.Configuration.LazyLoadingEnabled = false;
    }
    
    public MyAppContext(DbConnection connection)
        : base(connection, true)
    {
        this.Configuration.LazyLoadingEnabled = false;
    }
    
    public IDbSet<Product> Products { get; set; }
    public IDbSet<ProductTag> ProductTags { get; set; }
    public IDbSet<Tag> Tags { get; set; }   
}

Example business logic and Unit Test

In order to demonstrate the different test approaches, I need some code to put under test. I have ProductRepository class with a method GetProductsByTagName that gets all Product entities that are tagged with the specified tagName. The class constructor takes a MyAppContext instance, which is the Entity Framework DbContext class.

 
public class ProductRepository
{
    private MyAppContext _context;

    public ProductRepository(MyAppContext context)
    {
        _context = context;
    }

    // gets all products that have a matching tag associated with it.
    public IEnumerable<Product> GetProductsByTagName(string tagName)
    {
        // get the products that have a related tag with the specified name
        var products = _context.Products
            .Where(x => x.ProductTags.Any(pt => pt.Tag.Name == tagName))
            .ToList();

        return products;
    }
}

I also have a simple Unit Test that calls this method and tests that the correct Product is returned. In the test below, the _context variable is an instance of MyAppContext. Creating this variable for the two different test approaches is explained in the next section of the article.

 
[TestMethod]
public void GetProductsByTagName_Should_ReturnProduct()
{
    var productRepository = new ProductRepository(_context);

    IEnumerable<Product> products = productRepository.GetProductsByTagName("Tag 1");

    // check the correct product is retrieved
    Assert.AreEqual(1, products.Count());
    Assert.AreEqual("Product 1", products.First().Name);
}

Testing with Effort

The key to testing will Effort is that we use Effort to create a DbConnection object, which we then use to initialise our MyAppContext class:

 
DbConnection connection = Effort.DbConnectionFactory.CreateTransient();

var dbContext = new MyAppContext(connection);

Once we have the DbConnection instance, we can use it again and again to create new DbContext instances, which effectively simulate different connections to the same database. This is useful as we can create one connection to populate our database with test data, then create another fresh one to test with.

The DbConnection object is effectively the instance of the database, so if you create a new instance using DbConnectionFactory.CreateTransient(), it will not contain any data that you have added to a different instance. This means you need to hold onto the instance of the DbConnection object for the duration of your test.

The following code shows the Test Initialize method that runs before each test. It creates a new DbConnection object representing a new instance of our database; uses a new DbContext object to add test data to our Entity Mode;, then creates a new DbContext object for use in our test using the same instance of the DbConnection.

 
private MyAppContext _context;

[TestInitialize]
public void SetupTest()
{
    // create a new DbConnection using Effort
    DbConnection connection = Effort.DbConnectionFactory.CreateTransient();

    // create the DbContext class using the connection
    using (var context = new MyAppContext(connection))
    {
        // Add test data to the database here
        context.Products.Add(new Product() { Id = ... });
        // ... CODE OMMITTED ...
    }

    // use the same DbConnection object to create the context object the test will use.
    _context = new MyAppContext(connection);
}

When setting up our test data, it's worth mentioning that there's no need to initialize properties that point to related entites (e.g. Product.ProductTags and Tag.ProductTags). All we need to do is add individual entities and Effort will automatically associate these using the ID values, just as a real database would.

 
using (MyAppContext context = new MyAppContext(connection))
{
    // Add 2 Product entities
    context.Products.Add(new Product() { Id = new Guid("CEA4655C-..."), Name = "Product 1", ...
    context.Products.Add(new Product() { Id = new Guid("A4A989A4-..."), Name = "Product 2", ...

    // Add 3 Tag entities
    context.Tags.Add(new Tag() { Id = new Guid("D7FE98A2-..."), Name = "Tag 1", ...
    context.Tags.Add(new Tag() { Id = new Guid("52FEDB17-..."), Name = "Tag 2", ...
    context.Tags.Add(new Tag() { Id = new Guid("45312740-..."), Name = "Tag 3", ...

    // Associate Product 1 with Tag 1
    context.ProductTags.Add(new ProductTag() 
    { 
        ProductId = new Guid("CEA4655C-..."), 
        TagId = new Guid("D7FE98A2-...")
        ...
    });

    // Associate Product 1 with Tag 3
    context.ProductTags.Add(new ProductTag() 
    { 
         ProductId = new Guid("CEA4655C-..."),
         TagId = new Guid("45312740-...")
         ...
    });

    // Associate Product 2 with Tag 2
    context.ProductTags.Add(new ProductTag() 
    { 
        ProductId = new Guid("A4A989A4-..."),
        TagId = new Guid("52FEDB17-...")
        ...
    });
}

Reference

http://www.codeproject.com/Articles/460175/Two-strategies-for-testing-Entity-Framework-Effort