1

I'm creating a GenericRepository with EF and writing Unit Tests for the first time. Tests for GetAll() and Update() passed but Add() and Delete() failed. Why doesn't it Add? I'm pulling my hair out because it's one line of code and I couldn't figure it out. I'm using EF database First, Nunit, Nsubstitute.

Any advice is welcomed.

public class GenericDataRepository<T, C> : IGenericDataRepository<T, C> where T : class where C : DbContext, new() {

    protected C _context;
    protected IDbSet<T> _dbSet;

    public GenericDataRepository() {
        _context = new C();
        _dbSet = _context.Set<T>();
    }

    public GenericDataRepository(C context) {
        _context = context;
        _dbSet = context.Set<T>();
    }

    public virtual IQueryable<T> GetAll() {
        return _dbSet.AsQueryable<T>();
    }

    public virtual T Add(T entity) {
        return _dbSet.Add(entity);
    }

    public virtual void Update(T entity) {
        _context.Entry(entity).State = EntityState.Modified;
    }

    public virtual T Delete(T entity) {
        return _dbSet.Remove(entity);
    }

    public virtual void Save() {
        _context.SaveChanges();
    }

}

MyEntities

public partial class MyEntities : DbContext{
public MyEntities()
    : base("name=MyEntities")
{
}

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    throw new UnintentionalCodeFirstException();
}

public virtual DbSet<Customer> Cusotmers{ get; set; }

Tests

 public static class NSubstituteUtils {
    public static DbSet<T> CreateMockDbSet<T>(IQueryable<T> data = null)
        where T : class {
        var mockSet = Substitute.For<DbSet<T>, IQueryable<T>>();
        mockSet.AsNoTracking().Returns(mockSet);

        if (data != null) {
            var queryable = data.AsQueryable();

            // setup all IQueryable methods using what you have from "data"
            ((IQueryable<T>)mockSet).Provider.Returns(data.Provider);
            ((IQueryable<T>)mockSet).Expression.Returns(data.Expression);
            ((IQueryable<T>)mockSet).ElementType.Returns(data.ElementType);
            ((IQueryable<T>)mockSet).GetEnumerator().Returns(data.GetEnumerator());
        }

        return mockSet;
    }
}

static IQueryable<Customer> data;
[SetUp]
    public void Init() {
        data = new List<Customer> {
            new Customer {
                CUSTOMER = "333",
                CUSTOMERNAME = "no name"
            },
            new Customer {
                CUSTOMER = "555",
                CUSTOMERNAME = "test name"
            }
        }.AsQueryable();
    }
[Test]
    public void Add_Should_AddGenericT() {

        var mockSet = NSubstituteUtils.CreateMockDbSet<Customer>(data);
        var mockContext = Substitute.For<MyEntities>();
        mockContext.Set<Customer>().Returns(mockSet);

        var repo = new GenericDataRepository<Customer, MyEntities>(mockContext);

        var customer = new Customer {
            CUSTOMER1 = "123",
            CUSTOMERNAME = "test name"
        };
        var result = repo.Add(customer);        // issue here: result returns null which should be a Customer
        repo.Save();

        var customerList = repo.GetAll().ToList();
        Assert.AreEqual(3, customerList.Count); // failed. Expected 3 but was 2
    }
Firefly
  • 11
  • 3
  • Does your `Customer` type match the corresponding entity type? – David L Mar 30 '16 at 14:25
  • I didn't include all the properties in `Customer`. Some of the fields cannot be null. But `repo.Save()` didn't throw an exception. – Firefly Mar 30 '16 at 14:33
  • Sorry, to clarify, the `Customer` type MUST be the same type that is mapped to a table in entity framework. How have you mapped your context? Are you using Database-First or Code-First? – David L Mar 30 '16 at 14:34
  • Database-first. I am able to to add customer outside of the mocking/testing part. I added `MyEntities`. – Firefly Mar 30 '16 at 14:47
  • That's a really horrible repository implementation. Why did you create it at all? What do you think that you have achieved by using it instead of the Entity Framework interfaces? The purpose with the repository pattern is to reduce complexity by creating an abstraction. Your implementation does not do either of those. – jgauffin Mar 30 '16 at 15:04
  • @jgauffin I'm trying to make the application more testable. From the articles I read, several of them implemented with a generic repository. Am I towards the wrong direction? What is the preferred way to make it test driven with EF? – Firefly Mar 30 '16 at 18:03
  • Sorry if I sounded harsh. Repository pattern is the way to go, But generic repositories are leaky abstractions and hard to test. It's better if you do not expose `IQueryable` but instead write a method for each query that you need. It also makes it a lot easier to test. For more info, read my answer here: http://stackoverflow.com/a/17449231/70386 – jgauffin Mar 30 '16 at 18:31

1 Answers1

0

You're defining data variable here as IQueryable right away and using it to mock the _dbSet in your repository.

 data = new List<Customer> {
        new Customer {
            CUSTOMER = "333",
            CUSTOMERNAME = "no name"
        },
        new Customer {
            CUSTOMER = "555",
            CUSTOMERNAME = "test name"
        }
    }.AsQueryable();

So when you're doing a .Add() you're actually adding to an IQueryable which is a read-only interface.

Get rid of AsQueryable() from the data definition and use the actual List.

Dmitri M
  • 517
  • 6
  • 13
  • That seems to be the problem. `mockSet` returns as `IQueryable`. But I do need `IQueryable` to mock the Dbset. Do you happen to know how to return `mockSet` as a List? – Firefly Mar 30 '16 at 17:59
  • I'm not that familiar with NSubstitute, but when I use Moq for my unit tests and I had to implement both the `Add()` and `Remove()` methods to work on the IDbSet. Look at the two .Setup() methods here: [link](https://dotnetfiddle.net/6IJHty) You might have to do the same in your mocking framework. – Dmitri M Mar 30 '16 at 23:00