Как создать поддельный репозиторий с ассоциацией 1-ко-многим для MVC - PullRequest
6 голосов
/ 02 марта 2010

Я пытаюсь создать поддельный репозиторий для модульного тестирования с объектом класса, который имеет отношение один ко многим. Я использую ASP.NET MVC и Linq to SQL. Моя ссылка - книга Стивена Сандерсона "Pro ASP.NET MVC Framework".

Я создал классы сущностей с ассоциацией:

[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; }
}

И абстрактный интерфейс для хранилища:

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

Мне удалось создать поддельное хранилище для альбомов - 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(); }
    }

}

Это прекрасно работает, если я не пытаюсь получить доступ к трекам. Другими словами, Album.Title работает, но при доступе к Album.Tracks.Count () будет сгенерировано пустое исключение Reference. Я не был уверен, что Linq-To-SQL или Linq-To-Objects уловят ассоциацию и будут использовать ее автоматически с поддельными объектами или нет.

Проблема в том, что независимо от того, что я пытаюсь и не могу назначить треки для класса Album. Я знаю, что EntitySet основан на IEnumerable, но было сложно преобразовать список Enumerable в EntitySet.

Альбом ожидает EntitySet, но единственный способ, которым я смог передать это, - через собственного помощника:

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;
    }
}

Это так близко, как я пришел:

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(); }
    }

}

Однако, когда я пытаюсь получить доступ к Album.Tracks, я получаю

System.NullReferenceException: ссылка на объект не установлена ​​для экземпляра объекта.

Вот примерный / модульный тест:

    [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!");
    }

Я потратил несколько дней на поиск ответа на этот вопрос и не нашел ни одного примера, поэтому, если кто-то сможет опубликовать пример рабочего ответа, я уверен, что другие тоже будут заинтересованы.

Я очень новичок в MVC и модульном тестировании, поэтому, пожалуйста, будьте спокойны со мной. У меня также есть IoC и Moq, но я знаю об этом еще меньше, но я планирую использовать оба в этом проекте. Если есть лучший способ сделать это, то я весь слух! Спасибо.

Ответы [ 2 ]

2 голосов
/ 02 марта 2010

Класс Album кажется немного странным, особенно эта часть:

private EntitySet<Track> _tracks;

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

Закрытый элемент _tracks - это , никогда не присваивает значение, и вы вызываете метод Assign для него в установщике, который вызывает исключение. Кстати, компилятор должен был предупредить вас. Вы можете попытаться указать значение по умолчанию или проверить значение null в установщике перед вызовом метода Assign:

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

Кроме того, ваш метод расширения для преобразования IEnumerable<T> в EntitySet<T> выглядит неплохо.

И, наконец, когда вы назначаете фальшивые альбомы, вам нужно установить треки:

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();
1 голос
/ 03 марта 2010

Вот исправленный (рабочий) класс альбома:

[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; }
}

Проблема заключалась в том, что я не создавал экземпляр объекта Album, как указал Дарин. Модульный тест теперь работает правильно. Я протестировал как с поддельным хранилищем, так и с живыми данными, и все работает! Странно, что примеры MSDN (http://msdn.microsoft.com/en-us/library/bb425822.aspx) также не показывали назначение.

...