mimickd mimickd - 3 months ago 25
C# Question

Unit Testing for Web API, GET same ID

I'm at a lost for how to write a unit test for my web api GET by id method.

Here is what I have:

public void GetProduct_ShouldReturnSameID()
{
var context = new TestModelContext();
context.Products.Add(GetDemoProduct());

var controller = new ProductsController(context);
var result = controller.GetProduct(3) as OkNegotiatedContentResult<Product>;

Assert.IsNotNull(result);
Assert.AreEqual(3, result.Content.Id);
}


And my controller method I'm trying to test

public IHttpActionResult GetProduct(int id)
{
var product = (from t in db.Products.Include(t => t.Reviews)
.Where(t => t.Id == id)
select t);

if (product == null || product.Count() == 0)
{
return NotFound();
}

return Ok(product);
}


My test works find with my other controllers, but just not this one. I was wondering what is wrong with this? My test fails with an


"Expected: not null But was: null"


public class TestModelContext : IModelContext {
public TestModelContext() {
this.Products = new TestProductDbSet();
}
public DbSet<Product> Products { get; set; }
public int SaveChanges() {
return 0;
}
public void MarkAsModified(Product item) { }

public void Dispose() { }
}

public class TestProductDbSet : TestDbSet<Product> {
public override Product Find(params object[] keyValues) {
return this.SingleOrDefault(product => product.Id == (int)keyValues.Single());
}
}

public class TestDbSet<T> : DbSet<T>, IQueryable, IEnumerable<T>
where T : class
{
ObservableCollection<T> _data;
IQueryable _query;

public TestDbSet()
{
_data = new ObservableCollection<T>();
_query = _data.AsQueryable();
}

// ...

public override T Create()
{
return Activator.CreateInstance<T>();
}

public override TDerivedEntity Create<TDerivedEntity>()
{
return Activator.CreateInstance<TDerivedEntity>();
}

public override ObservableCollection<T> Local
{
get { return new ObservableCollection<T>(_data); }
}

Type IQueryable.ElementType
{
get { return _query.ElementType; }
}

System.Linq.Expressions.Expression IQueryable.Expression
{
get { return _query.Expression; }
}

IQueryProvider IQueryable.Provider
{
get { return _query.Provider; }
}

System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return _data.GetEnumerator();
}

IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return _data.GetEnumerator();
}
}


GetDemoProduct:

Product GetDemoProduct()
{
return new Product() { Id = 3, Name = "Name", Reviews = null };
}

Answer

According to the sample code there is no code path that would result in the method under test returning null.

It's either going to return a NotFoundResult or a OkNegotiatedContentResult<Product>

Given that it is possible for the method under test to return a NotFoundResult , if the if (product == null || product.Count() == 0) condition is met and the method does indeed return not found result, then the following in your test

...as OkNegotiatedContentResult<Product>;

trying to cast NotFoundResult as OkNegotiatedContentResult<Product> will cause the result to be null.

You should recheck your setup/configuration of your TestModelContext as your linq call is causing the product variable to be null which in turn causes the NotFoundResult to be returned.

UPDATE:

Ok was able to start testing it based on your updated details and found an issue i should have noticed earlier.

First I was getting an error when added fake Product to list and had to update the TestDbSet base class to include added entities. I'll assume it was omitted in the sample code.

public override T Add(T entity) {
    _data.Add(entity);
    return entity;
}

next in the method under test, given the name the method and the expectation in the test, it should be returning a single Product. You were returning the query which would also result in null when you did the cast in the test.

public IHttpActionResult GetProduct(int id) {
    var product = (from t in db.Products.Include(t => t.Reviews)
                          .Where(t => t.Id == id)
                   select t);

    if (product == null || product.Count() == 0) {
        return NotFound();
    }

    return Ok(product.First());
}

When the above two changes were made, the test passed as expected.

Comments