Как я могу издеваться над анализом навигационных свойств Entity Framework? - PullRequest
3 голосов
/ 11 октября 2010

Я пытаюсь протестировать свой репозиторий, используя фиктивный контекст в памяти.

Я реализую это с помощью словаря в памяти, как и большинство людей. Это реализует элементы в моем интерфейсе репозитория для добавления, удаления, поиска и т. Д., Работая с коллекцией в памяти.

Это прекрасно работает в большинстве сценариев:

[TestMethod]
public void CanAddPost()
{
    IRepository<Post> repo = new MockRepository<Post>();
    repo.Add(new Post { Title = "foo" });
    var postJustAdded = repo.Find(t => t.Title == "foo").SingleOrDefault();
    Assert.IsNotNull(postJustAdded); // passes
}

Тем не менее, у меня есть следующий тест, который я не могу пройти с фиктивным репозиторием (прекрасно работает для репозитория SQL).

Считайте, что у меня есть три хранилища:

  1. Сообщения (обрабатывает сообщения с пользовательским контентом, например, вопрос StackOverflow).
  2. Места (места в мире, например, "Лос-Анджелес").
  3. LocationPosts (таблица соединений для обработки много-много между сообщениями / местоположениями).

Сообщения могут быть добавлены в никуда, или они также могут быть добавлены с определенным местоположением.

Вот мой тест:

[TestMethod]
public void CanAddPostToLocation()
{
   var location = locationRepository.FindSingle(1); // Get LA
   var post = new Post { Title = "foo", Location = location }; // Create post, with LA as Location.
   postRepository.Add(post); // Add Post to repository

   var allPostsForLocation = locationPostRepository.FindAll(1); // Get all LA posts.
   Assert.IsTrue(allPostsForLocation.Contains(post)); // works for EF, fails for Mock.
}

По сути, при использовании «настоящего» EF / SQL-репозитория, когда я добавляю сообщение в определенное местоположение, EntityFramework достаточно умен, чтобы добавить запись «LocationPost», из-за ассоциации в EDMX (навигация «LocationPosts») собственность на объект "Почта")

Но как я могу сделать мой репозиторий Mock достаточно умным, чтобы "подражать" этому интеллекту EF?

Когда я делаю «Добавить» в моём репозитории, это просто добавляет в словарь. У него нет смысла говорить: «О, подожди, у тебя есть зависимая ассоциация, позволь мне добавить это в ДРУГОЙ репозиторий для тебя».

Мой Mock Repository является общим, поэтому я не знаю, как поместить туда смарты.

Я также смотрел на создание FakeObjectContext / FakeObjectSet (как советовала Джули Лерман в ее блоге), но это все еще не охватывает этот сценарий.

У меня такое чувство, что моё решение недостаточно хорошо. Может кто-нибудь помочь или предоставить актуальную статью о том, как правильно смоделировать репозиторий Entity Framework 4 / SQL Server, охватывающий мой сценарий?

Проблема core в том, что у меня есть один репозиторий на совокупный корень (что хорошо, но также и мое падение).

Итак Пост и Местоположение оба являются агрегированными корнями, но не владеют LocationPosts .

Таким образом, они представляют собой 3 отдельных хранилища, а в сценарии в памяти - 3 отдельных словаря. Я думаю, что мне не хватает «клея» между ними в моем репо в памяти.

EDIT

Отчасти проблема в том, что я использую Pure POCO (без генерации кода EF). У меня также нет отслеживания изменений (нет отслеживания на основе снимков, нет прокси-классов).

У меня сложилось впечатление, что именно здесь происходят "смарты".

В данный момент я изучаю опцию делегата. Я выставляю событие в моем Generic Mock Repository (void, принимает generic T, являющееся Entity), которое я вызываю после «Add». Затем я подписываюсь на это событие в моем «Репозитории сообщений», где я планирую добавить связанные объекты в другие репозитории.

Это должно работать. В качестве ответа поставлю в качестве ответа.

Однако я не уверен, что это лучшее решение, но опять же, это только для удовлетворения насмешек (код не будет использоваться для реальной функциональности).

1 Ответ

3 голосов
/ 12 октября 2010

Как я уже говорил в своем EDIT, я исследовал опцию делегата, которая успешно работала.

Вот как я это сделал:

namespace xxxx.Common.Repositories.InMemory // note how this is an 'in-memory' repo
{
   public class GenericRepository<T> : IDisposable, IRepository<T> where T : class
   {
      public delegate void UpdateComplexAssociationsHandler<T>(T entity);
      public event UpdateComplexAssociationsHandler<T> UpdateComplexAssociations;

      // ... snip heaps of code

      public void Add(T entity) // method defined in IRepository<T> interface
      {
         InMemoryPersistence<T>().Add(entity); // basically a List<T>
         OnAdd(entity); // fire event
      }

      public void OnAdd(T entity)
      {
         if (UpdateComplexAssociations != null) // if there are any subscribers...
            UpdateComplexAssociations(entity); // call the event, passing through T
      }
   }
}

Затем в моей памяти "Post Repository" (которая наследуется от вышеуказанного класса).

public class PostRepository : GenericRepository<Post>
{
   public PostRepository(IUnitOfWork uow) : base(uow)
   {
      UpdateComplexAssociations += 
                  new UpdateComplexAssociationsHandler<Post>(UpdateLocationPostRepository);
   }

   public UpdateLocationPostRepository(Post post)
   {
      // do some stuff to interrogate the post, then add to LocationPost.
   }
}

Вы также можете подумать: «Держись, PostRepository получает из GenericRepository, так почему же вы используете делегаты, почему бы вам не переопределить Add?» И ответом является то, что метод «Add» является реализацией интерфейса IRepository - и, следовательно, не может быть виртуальным.

Как я уже сказал, не лучшее решение - но это насмешливый сценарий (и хороший пример для делегатов). У меня сложилось впечатление, что не так много людей идут «так далеко» с точки зрения насмешек, чистых POCO и репозитория / единиц работы (без отслеживания изменений в POCO).

Надеюсь, это поможет кому-то еще.

...