views:

233

answers:

2

I'm trying to create a fake repository for unit testing with a class object that has a one to many relationship. I'm using ASP.NET MVC and Linq to SQL. My reference is Steven Sanderson's "Pro ASP.NET MVC Framework" book.

I've created the Entity Classes with the association:

[Table(Name = "Albums")]
public class Album
{
    [Column(IsDbGenerated = true, IsPrimaryKey = true)]
    public int AlbumID { get; set; }
    [Column]
    public string Title { get; set; }

    private EntitySet<Track> _tracks;

    [Association(Storage = "_tracks", ThisKey = "AlbumID", OtherKey = "AlbumID")]
    public EntitySet<Track> Tracks
    {
        get { return _tracks; }
        set { _tracks.Assign(value); }
    }
}

[Table(Name = "Tracks")]
public class Track
{
    [Column(IsPrimaryKey = true)]
    public int TrackID { get; set; }
    [Column]
    public string Title { get; set; }
    [Column]
    public int AlbumID { get; set; }
}

And an abstract interface for the repository:

public interface IMusicRepository
{
    IQueryable<Album> Albums { get; }
    IQueryable<Track> Tracks { get; }
}

I was able to create a fake repository for Albums - FakeMusicRepository:

public class FakeMusicRepository : IMusicRepository
{

    private static IEnumerable<Track> fakeTracks = new List<Track>
    {
        new Track {AlbumID = 1, TrackID = 1, Title = "Flood"},
        new Track {AlbumID = 1, TrackID = 2, Title = "Liquid"},
        new Track {AlbumID = 2, TrackID = 1, Title = "Song 1"},
        new Track {AlbumID = 2, TrackID = 2, Title = "Song 2"}
    }.AsEnumerable();

    private static IQueryable<Album> fakeAlbums = new List<Album>
    {
          new Album {AlbumID = 1, Title = "Jars of Clay"},
          new Album {AlbumID = 2, Title = "Wind in the Wheat"}
    }.AsQueryable();


    public IQueryable<Album> Albums
    {
        get { return fakeAlbums; }
    }

    public IQueryable<Track> Tracks
    {
        get { return fakeTracks.AsQueryable(); }
    }

}

Which works just fine as long as I don't try to access any tracks. In other words Album.Title works but accessing Album.Tracks.Count() will generate a null Reference exception. I wasn't sure if Linq-To-SQL or Linq-To-Objects would pickup the association and use it automatically with the fake objects or not.

The problem is that no matter what I try and can't seem to be able to assign the Tracks to Album class. I know that EntitySet is based on IEnumerable but it's been a challenge to cast the Enumerable list to a EntitySet.

Album is expecting an EntitySet but the only way I've been able to pass that in is via a custom helper:

public static class EntityCollectionHelper
{
    public static EntitySet<T> ToEntitySet<T>(this IEnumerable<T> source) where T : class
    {
        EntitySet<T> set = new EntitySet<T>();
        set.AddRange(source);
        return set;
    }
}

This is as close as I've come:

public class FakeMusicRepository2 : IMusicRepository
{
    private static IEnumerable<Track> fakeTracks = new List<Track>
    {
        new Track {AlbumID = 1, TrackID = 1, Title = "Flood"},
        new Track {AlbumID = 1, TrackID = 2, Title = "Liquid"},
        new Track {AlbumID = 2, TrackID = 1, Title = "Song 1"},
        new Track {AlbumID = 2, TrackID = 2, Title = "Song 2"}
    }.AsEnumerable();

    private static EntitySet<Track> Tracks1 = (from t in fakeTracks where t.AlbumID == 1 select t).ToEntitySet();
    private static EntitySet<Track> Tracks2 = (from t in fakeTracks where t.AlbumID == 2 select t).ToEntitySet();

    private static IQueryable<Album> fakeAlbums = new List<Album>
    {
          new Album {AlbumID = 1, Title = "Jars of Clay", Tracks=Tracks1},
          new Album {AlbumID = 2, Title = "Wind in the Wheat", Tracks=Tracks2}
    }.AsQueryable();


    public IQueryable<Album> Albums
    {
        get { return fakeAlbums; }
    }

    public IQueryable<Track> Tracks
    {
        get { return fakeTracks.AsQueryable(); }
    }

}

However when I try to access Album.Tracks I get

System.NullReferenceException : Object reference not set to an instance of an object.

Here is the implamentation / unit test:

    [Test]
    public void Test_FakeMusicRepository()
    {
        // Arrange: Setup Repository
        var dc = new FakeMusicRepository2();

        // Act:
        var albums = dc.Albums.AsQueryable();
        // Load the first test location
        Album album = dc.Albums.First();

        // Assert: That there are records in the Album Object
        Assert.Greater(albums.Count(), 0, "No Albums Records Returned!");

        //Assert that our first Album is not Null
        Assert.IsNotNull(album, "returned Album is Null!");
        // Assert: That we have the correct Album
        Assert.AreEqual(album.AlbumID, 1, "Returned Album ID is not correct!");

        // Try to Count the related sub table (Tracks)
        var trackCount = album.Tracks.Count();

        // Try to get the first Track
        var track1 = album.Tracks.FirstOrDefault();


        // Assert: The Album has Tracks 
        Assert.Greater(trackCount, 0, "Album has no Tracks");

        // Assert: That First track is not Null
        Assert.IsNotNull(track1, "Track1 object was Null!");

        // Assert: That Track1 has data
        Assert.IsNotNull(track1.TrackID, "Track1's ID was Null!");
    }

I've spent days searching for an answer to this question and haven't found any examples so if someone would be able to post a working answer example I'm sure others would be interested as well.

I am very new to MVC and Unit Testing so please go easy with me. I have IoC and Moq on hand as well but I know even less about these but I'm planning on using both with this project. If there is a better way to do this then I'm all ears! Thanks.

+1  A: 

The Album class seems a bit strange and especially this part:

private EntitySet<Track> _tracks;

[Association(Storage = "_tracks", ThisKey = "AlbumID", OtherKey = "AlbumID")]
public EntitySet<Track> Tracks
{
    get { return _tracks; }
    set { _tracks.Assign(value); }
}

The _tracks private member is never assigned a value and you call the Assign method on it in the setter which throws the exception. By the way the compiler should have warned you. You could try providing a default value or check for null in the setter before calling the Assign method:

private EntitySet<Track> _tracks = new EntitySet<Track>();

Other than that your extension method to convert an IEnumerable<T> to EntitySet<T> seems nice.

And finally when you assign the fake albums you need to set the tracks:

private static IQueryable<Album> fakeAlbums = new List<Album>
{
      new Album 
      {
          AlbumID = 1, 
          Title = "Jars of Clay", 
          Tracks = fakeTracks.ToEntitySet() 
      },
      new Album 
      {
          AlbumID = 2, 
          Title = "Wind in the Wheat",
          Tracks = fakeTracks.ToEntitySet() 
      }
}.AsQueryable();
Darin Dimitrov
Thanks Darivn - adding _tracks = new EntitySet<Track>(); solved it. As you said I wasn't assigning a value to _tracks. The MSDN examples and the books I have show the code this way without the assignment - I guess the Linq To Sql takes care of that for you. Yes the compier was throwing out warnings but becuase the Linq to SQL was working I figured everything was ok. You are correct about forgetting to assign Tracks in the Album object. I missed pasting in a block of code which shows that. I've added the missing block of code and I'll throw up my unit test implementation code as well. Thanks!
ColinICN
A: 

Here is the corrected (working) Album Class:

[Table(Name = "Albums")]
public class Album
{
    [Column(IsDbGenerated = true, IsPrimaryKey = true)]
    public int AlbumID { get; set; }
    [Column]
    public string Title { get; set; }

    private EntitySet<Track> _tracks = new EntitySet<Track>();

    [Association(Storage = "_tracks", ThisKey = "AlbumID", OtherKey = "AlbumID")]
    public EntitySet<Track> Tracks
    {
        get { return _tracks; }
        set { _tracks.Assign(value); }
    }
}

[Table(Name = "Tracks")]
public class Track
{
    [Column(IsPrimaryKey = true)]
    public int TrackID { get; set; }
    [Column]
    public string Title { get; set; }
    [Column]
    public int AlbumID { get; set; }
}

The problme was that I wasn't creating an instance of the Album object as Darin pointed out. The unit test now works correctly. I've tested both with the fake repository as well as the live data and everything works! It's odd that the MSDN examples (http://msdn.microsoft.com/en-us/library/bb425822.aspx) didn't show the assignment either.

ColinICN