How to create a fake repository with a 1-to-many association for MVC
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.
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();
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.
精彩评论