2
\$\begingroup\$

This is a follow-up of this question regarding MVC application architecture fit for automatic testing.

I have rewritten the DI code based on the answer provided there and also created some tests to see if I can properly benefit from the newly refactored code. I will repeat here the application structure, so that the question can be understood without reading the original question:

  1. Common assembly - contains generic functionalities, including models definition
  2. Fetch job - daily job that fetches and processes raw article data
  3. Web application - the actual Web application that allows users to see processed data
  4. Test project - an automatic testing project based on NUnit that currently contains only integrative tests (no mocking, no real unit tests)

The actual code:

1) Common DI bindings (for Job and Web App mainly)

public class NinjectCommon
{
 public static void RegisterCommonServices(IKernel kernel)
 {
 kernel.Bind<IUnitOfWork>().To<UnitOfWork>();
 kernel.Bind<IAggregatorContext>().ToFactory(() => new AggregatorContextProvider());
 kernel.Bind<IEntitiesCache>().To<EntitiesCache>().InSingletonScope();
 kernel.Bind<IExtDictionaryParser>().To<ExtDictionaryParser>().InSingletonScope();
 kernel.Bind(typeof(IRepository<>)).To(typeof(Repository<>));
 }
}

2) Repository definition

public interface IRepository
{
}
public interface IRepository<TEntity> : IRepository
 where TEntity : class
{
 IQueryable<TEntity> AllNoTracking { get; }
 IQueryable<TEntity> All { get; }
 TEntity Get(int id);
 void Insert(TEntity entity);
 void Delete(TEntity entity);
 void Update(TEntity entity);
}
public class Repository<T> : IRepository<T> where T : class, new()
{
 private IAggregatorContext _context;
 public Repository(IAggregatorContext context)
 {
 this._context = context;
 }
 public IQueryable<T> All
 {
 get { return _context.Set<T>().AsQueryable(); }
 }
 public IQueryable<T> AllNoTracking
 {
 get { return _context.Set<T>().AsNoTracking(); }
 }
 public T Get(int id)
 {
 return _context.Set<T>().Find(id);
 }
 public void Delete(T entity)
 {
 if (_context.Entry(entity).State == EntityState.Detached)
 _context.Set<T>().Attach(entity);
 _context.Set<T>().Remove(entity);
 }
 public void Insert(T entity)
 {
 _context.Set<T>().Add(entity);
 }
 public void Update(T entity)
 {
 _context.Set<T>().Attach(entity);
 _context.Entry(entity).State = EntityState.Modified;
 }
}

3) Unit of work

public class UnitOfWork : IUnitOfWork
{
 #region Members
 private IAggregatorContext _context;
 #endregion
 #region Properties
 public IRepository<Lexem> LexemRepository { get; private set; }
 public IRepository<Word> WordRepository { get; private set; }
 public IRepository<Synset> SynsetRepository { get; private set; }
 // other repositories come here (removed for brevity)
 #endregion
 #region Constructor
 public UnitOfWork(IAggregatorContext context,
 IRepository<Lexem> lexemRepository, IRepository<Word> wordRepository, IRepository<Synset> synsetRepository,
 /* other repositories params here */)
 {
 this._context = context;
 LexemRepository = lexemRepository;
 WordRepository = wordRepository;
 SynsetRepository = synsetRepository;
 }
 #endregion
 #region Methods
 public IRepository<T> GetRepository<T>()
 where T: class
 {
 Type thisType = this.GetType();
 foreach (var prop in thisType.GetProperties())
 {
 var propType = prop.PropertyType;
 if (!typeof(IRepository).IsAssignableFrom(propType))
 continue;
 var repoType = propType.GenericTypeArguments[0];
 if (repoType == typeof(T))
 return (IRepository<T>) prop.GetValue(this);
 }
 throw new ArgumentException(String.Format("No repository of type {0} found", typeof(T).FullName));
 }
 public void SaveChanges()
 {
 _context.SaveChanges();
 }
 public bool SaveChangesEx()
 {
 return _context.SaveChangesEx();
 }
 #endregion
}

4) Fetch job DI setup

class Program
{
 #region Members
 private static Logger logger = LogManager.GetCurrentClassLogger();
 #endregion
 #region Properties
 private static IUnitOfWork _UnitOfWork { get; set; }
 private static IKernel _Kernel { get; set; }
 private static IFetchJob _FetchJob { get; set; }
 #endregion
 #region Methods
 private static void init()
 {
 // setup DI
 _Kernel = new StandardKernel();
 _Kernel.Load(Assembly.GetExecutingAssembly());
 NinjectCommon.RegisterCommonServices(_Kernel);
 registerServices();
 _UnitOfWork = _Kernel.Get<IUnitOfWork>();
 _FetchJob = _Kernel.Get<IFetchJob>();
 }
 private static void registerServices()
 {
 _Kernel.Bind<INlpUtils>().To<NlpUtils>();
 _Kernel.Bind<IFetchJob>().To<FetchJob>().InSingletonScope();
 }
 static void Main(string[] args)
 {
 init();
 Utils.InitNLogConnection();
 logger.LogEx(LogLevel.Info, "FetchJob started");
 MappingConfig.CreateMappings();
 try
 {
 _FetchJob.FetchArticleData();
 }
 catch (Exception exc)
 {
 logger.LogEx(LogLevel.Fatal, "Global unhandled exception", exc);
 }
 finally
 {
 logger.LogEx(LogLevel.Info, "FetchJob stopped");
 }
 }
 #endregion
}

5) Web application DI setup

public static class NinjectWebCommon 
{
 private static readonly Bootstrapper bootstrapper = new Bootstrapper();
 /// <summary>
 /// Starts the application
 /// </summary>
 public static void Start() 
 {
 DynamicModuleUtility.RegisterModule(typeof(OnePerRequestHttpModule));
 DynamicModuleUtility.RegisterModule(typeof(NinjectHttpModule));
 bootstrapper.Initialize(CreateKernel);
 }
 /// <summary>
 /// Stops the application.
 /// </summary>
 public static void Stop()
 {
 bootstrapper.ShutDown();
 }
 /// <summary>
 /// Creates the kernel that will manage your application.
 /// </summary>
 /// <returns>The created kernel.</returns>
 private static IKernel CreateKernel()
 {
 var kernel = new StandardKernel();
 try
 {
 kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
 kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();
 RegisterServices(kernel);
 return kernel;
 }
 catch
 {
 kernel.Dispose();
 throw;
 }
 }
 /// <summary>
 /// Load your modules or register your services here!
 /// </summary>
 /// <param name="kernel">The kernel.</param>
 private static void RegisterServices(IKernel kernel)
 {
 NinjectCommon.RegisterCommonServices(kernel);
 // other bindings come here
 } 
}

6) Automatic tests projects DI setup

All test classes inherit a base test class that provides some functionality for all test groups:

public abstract class BaseTest
{
 #region Variables
 protected IKernel _Kernel { get; private set; }
 [Inject]
 public IUnitOfWork UnitOfWork { get; private set; }
 #endregion
 #region Constructor
 protected BaseTest()
 {
 _Kernel = new NSubstituteMockingKernel();
 _Kernel.Load(Assembly.GetExecutingAssembly());
 NinjectCommon.RegisterCommonServices(_Kernel);
 RegisterServices();
 UnitOfWork = _Kernel.Get<IUnitOfWork>();
 // used to inject into properties
 _Kernel.Inject(this);
 }
 #endregion
 #region Abstract methods
 [SetUp]
 protected virtual void Init()
 {
 MappingConfig.CreateMappings();
 Utils.InitNLogConnection();
 }
 protected virtual void RegisterServices()
 {
 _Kernel.Bind<INlpUtils>().To<NlpUtils>();
 }
 [TearDown]
 protected abstract void TearDown();
 #endregion
}

7) A test that rebinds to a mockup type that replaces all real type functionality

public class ExtDictionaryParserTest : BaseTest
{
 #region Properties
 [Inject]
 public IExtDictionaryParser ExtDictionaryParser { get; set; }
 #endregion
 #region Overrides
 protected override void TearDown()
 {
 }
 protected override void RegisterServices()
 {
 base.RegisterServices();
 _Kernel.Rebind<IExtDictionaryParser>().To<ExtDictionaryParserMockup>();
 }
 #endregion
 #region Tests
 [Test]
 [Category(Constants.Fast)]
 public void ExtDictionaryParse()
 {
 bool isForeign = false;
 Assert.AreEqual(ExtDictionaryParser.WordFromExtDictionary("pagina", out isForeign), "pagina");
 Assert.IsFalse(isForeign);
 Assert.AreEqual(ExtDictionaryParser.WordFromExtDictionary("pagini", out isForeign), "pagina");
 Assert.IsFalse(isForeign);
 Assert.AreEqual(ExtDictionaryParser.WordFromExtDictionary("pages", out isForeign), "page");
 Assert.IsTrue(isForeign);
 Assert.IsNull(ExtDictionaryParser.WordFromExtDictionary("nonexisting_word", out isForeign));
 }
 #endregion
}

8) A test that replaces a repository return function that is used by another function

public class EntitiesCacheTest : BaseTest
{
 #region Tests
 [Test]
 [Category(Constants.Fast)]
 public void BaseWordExists()
 {
 var dummyList = new List<Lexem>() {
 new Lexem() { Word = "something" },
 new Lexem() { Word = "other" },
 new Lexem() { Word = "nothing" }
 };
 var unitOfWorkSubst = Substitute.For<IUnitOfWork>();
 unitOfWorkSubst.LexemRepository.AllNoTracking.Returns(dummyList.AsQueryable());
 _Kernel.Rebind<IUnitOfWork>().ToConstant(unitOfWorkSubst);
 _Kernel.Rebind<IEntitiesCache>().To<EntitiesCache>().InSingletonScope();
 var entitiesCache = _Kernel.Get<IEntitiesCache>();
 Assert.IsTrue(entitiesCache.BaseWordExists("something"));
 Assert.IsTrue(entitiesCache.BaseWordExists("other"));
 Assert.IsFalse(entitiesCache.BaseWordExists("authentic"));
 Assert.IsFalse(entitiesCache.BaseWordExists("Something"));
 Assert.IsFalse(entitiesCache.BaseWordExists("NonExistent"));
 }
 #endregion
}

My questions is:

am I using correctly DI in the above automated tests? Should I improve something to what is already there? As the application grows, I expect to have hundreds of tests, so managing them correctly is important.

asked Jan 9, 2016 at 13:41
\$\endgroup\$

0

Know someone who can answer? Share a link to this question via email, Twitter, or Facebook.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.