khan88
khan88

Reputation: 109

Mocking with moq, trying to pass an object to constructor having multiple parameter

I am trying to mock a method that returns a IEnumerable set of data, like a list of all codes.

There is an Interface ISystemService.cs that contains this method, a service class called SystemService.cs that has the method definition.

System under test is:

 public static class CacheKeys
    {
      public const string ALLCURRENCYCODES = "CurrencyCodes";
    }
 public interface ICacheManager
    {
        T Get<T>(string key);
        void Set(string key, object data, int cacheTime);
        void Clear();
    }
public interface ISessionManager
{
}
public interface IApplicationSettings
    {
        string LoggerName { get; }
        int CacheTimeout { get; }
    }
public class EFDbContext : DbContext
    {
    public DbSet<CurrencyCode> CurrencyCodes { get; set; }
    }
public class CurrencyCode 
    {
        public string Code { get; set; }
        public string Description { get; set; }
        public decimal CurrencyUnit { get; set; }
        public int? DecimalPlace { get; set; }
        public string BaseCurrencyCode { get; set; }
    }   
public interface ISystemService
    {
        IEnumerable<CurrencyCode> GetAllCurrencyCodes();
    }
//SystemService.cs
public class SystemService : ISystemService
    {

        private readonly EFDbContext db;
        private readonly ICacheManager cacheManager;
        private readonly ISessionManager sessionManager;
        private readonly IApplicationSettings appSettings;

        public SystemService(EFDbContext dbContext, ICacheManager cacheManager, ISessionManager sessionManager, IApplicationSettings appSettings)
        {
            db = dbContext;
            this.cacheManager = cacheManager;
            this.sessionManager = sessionManager;
            this.appSettings = appSettings;
        }
      public IEnumerable<CurrencyCode> GetAllCurrencyCodes()
        {
            var allCurrencyCodes = cacheManager.Get<IEnumerable<CurrencyCode>>(CacheKeys.ALLCURRENCYCODES);

            if (allCurrencyCodes == null)
            {
                allCurrencyCodes = db.CurrencyCodes.ToList();

                cacheManager.Set(CacheKeys.ALLCURRENCYCODES, allCurrencyCodes, appSettings.CacheTimeout);
            }

            return allCurrencyCodes;
        }

Test Method

[TestMethod]
        public void testCacheMiss()
        {
            List<CurrencyCode> currencycodes = new List<CurrencyCode>()
         {
            new CurrencyCode(){Id = 1, Code = "IND", Description = "India"},
            new CurrencyCode(){Id = 2, Code = "USA", Description = "UnitedStates"},
            new CurrencyCodes(){Id = 3, Code = "UAE", Description = "ArabEmirates"}
         };
            var mockEfContext = new Mock<EFDbContext>();
            var mockCacheManager = new Mock<ICacheManager>();
            var mockSessionManager = new Mock<ISessionManager>();
            var mockAppSettings = new Mock<IApplicationSettings>();

            // Setups for relevant methods of the above here, e.g. to test a cache miss
            mockEfContext.SetupGet(x => x.CurrencyCodes)
               .Returns(currencycodes); // Canned currencies
            mockCacheManager.Setup(x => x.Get<IEnumerable<CurrencyCode>>(It.IsAny<string>()))
               .Returns<IEnumerable<CurrencyCodes>>(null); // Cache miss

            // Act
            var service = new SystemService(mockEfContext.Object, mockCacheManager.Object,
               mockSessionManager.Object, mockAppSettings.Object);
            var codes = service.GetAllCodes();

            // Assert + Verify
            mockCacheManager.Verify(x => x.Get<IEnumerable<CurrencyCodes>>(
               It.IsAny<string>()), Times.Once, "Must always check cache first");
            mockEfContext.VerifyGet(x => x.CurrencyCodes,
               Times.Once, "Because of the simulated cache miss, must go to the Db");
            Assert.AreEqual(currencycodes.Count, codes.Count(), "Must return the codes as-is");

Since the defined constructor does not accept one parameter, how to pass the object as parameter? Please advice

Upvotes: 4

Views: 4805

Answers (2)

StuartLC
StuartLC

Reputation: 107387

If CodeService is under test, then you want to be mocking its dependencies, not the CodeService itself.

You'll need to provide Mocks for all of the dependencies of CodeService to the constructor, i.e.:

     var currencycodes = new List<SomeCodes>
     {
        new CurrencyCodes(){Id = 1, Code = "IND", Description = "India"},
        new CurrencyCodes(){Id = 2, Code = "USA", Description = "UnitedStates"},
        new CurrencyCodes(){Id = 3, Code = "UAE", Description = "ArabEmirates"}
     };
     var mockEfContext = new Mock<EFDbContext>();
     var mockCacheManager = new Mock<ICacheManager>();
     var mockSessionManager = new Mock<ISessionManager>();
     var mockAppSettings = new Mock<IApplicationSettings>();

     // Setups for relevant methods of the above here, e.g. to test a cache miss
     mockEfContext.SetupGet(x => x.SomeCodes)
        .Returns(currencycodes); // Canned currencies
     mockCacheManager.Setup(x => x.Get<IEnumerable<SomeCodes>>(It.IsAny<string>()))
        .Returns<IEnumerable<SomeCodes>>(null); // Cache miss

     // Act
     var service = new CodeService(mockEfContext.Object, mockCacheManager.Object,
        mockSessionManager.Object, mockAppSettings.Object);
     var codes = service.GetAllCodes();

     // Assert + Verify
     mockCacheManager.Verify(x => x.Get<IEnumerable<SomeCodes>>(
        It.IsAny<string>()), Times.Once, "Must always check cache first");
     mockEfContext.VerifyGet(x => x.SomeCodes,
        Times.Once, "Because of the simulated cache miss, must go to the Db");
     Assert.AreEqual(currencycodes.Count, codes.Count(), "Must return the codes as-is");

Edit If you however mean that the next layer up of your code is under test, the principal is the same:

var mockCodeService = new Mock<ICodeService>();
mockCodeService.Setup(x => x.GetAllCodes())
    .Returns(currencycodes); // Now we don't care whether this is from cache or db 

var higherLevelClassUsingCodeService = new SomeClass(mockCodeService.Object);
higherLevelClassUsingCodeService.DoSomething();

mockCodeService.Verify(x => x.GetAllCodes(), Times.Once); // etc

Edit 2
I've fixed a couple of typos in the code, and assuming CurrencyCodes inherits SomeCodes and that your cache key is a string, and pushed it up onto a Git Gist here with the corresponding cache miss unit test as well. (I've used NUnit, but it isn't really relevant here)

Upvotes: 2

Simon Whitehead
Simon Whitehead

Reputation: 65087

allCodes is your service.. its the mock you need to be working with. You shouldn't be creating a concrete instance of your ICodeService.. your mock exists to fill that role.

So, remove this:

var service = new CodeService(allCodes.object);

Your next line should be:

var code = allCodes.Object.GetAllCodes();

But then.. this test seems completely redundant after that.. since you appear to be testing your mock..

Also, allCodes should be called serviceMock.. as that makes more sense.

Upvotes: 1

Related Questions