Cannot create Moq test with Interface while making the function virtual works - c#

I'm trying to test this class:
public class Tasks : ITaskEnumerableProvider
{
protected string ConnectionString;
DAL_EFCore.AdventureWorks2017Context CurrentContext;
public Tasks(DAL_EFCore.AdventureWorks2017Context currentContext)
{
CurrentContext = currentContext;
}
public Tasks(string connectionString)
{
ConnectionString = connectionString;
}
public DAL_EFCore.AdventureWorks2017Context GetContext()
{
if (CurrentContext != null)
return CurrentContext;
else
{
var serviceCollection = new ServiceCollection()
.AddDbContextPool<DAL_EFCore.AdventureWorks2017Context>
(
options => options.UseSqlServer(ConnectionString)
);
var serviceProvider = serviceCollection.BuildServiceProvider();
CurrentContext = serviceProvider.GetService<DAL_EFCore.AdventureWorks2017Context>();
return CurrentContext;
}
}
public IEnumerable<SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders> SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders()
{
return GetContext().Employee
.GroupBy(e => e.Gender)
.Select(n => new SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders
{
Gender = n.Key,
Count = n.Count()
})
.ToList();
}
}
And this one:
public class DataReports : IDataReports
{
protected ITaskEnumerableProvider TaskProvider;
protected string ConnectionString;
protected string DalModeSelected;
public DataReports() {}
public DataReports(DBConfig config)
{
ConnectionString = config.ConnectionString;
DalModeSelected = config.DAL;
}
public ITaskEnumerableProvider GetTaskProvider()
{
switch (DalModeSelected)
{
case "ADO":
return new ADOTaskProvider.Tasks(ConnectionString);
case "EFCore":
return new EFCoreTaskProvider.Tasks(ConnectionString);
default:
throw new FormatException("The format of the variable which represend the selected DAL was not correct");
}
}
public IEnumerable<SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders> SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders()
{
return GetTaskProvider().SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders().ToList();
}
I'm testing with this code:
[TestMethod]
public void SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrdersTest()
{
// Setup Mock Data and context
var options = new DbContextOptionsBuilder<DAL_EFCore.AdventureWorks2017Context>()
.UseInMemoryDatabase(databaseName: "SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrdersTest")
.Options;
using (var context = new DAL_EFCore.AdventureWorks2017Context(options))
{
InsertData(options);
}
using (var context = new DAL_EFCore.AdventureWorks2017Context(options))
{
// Mock EFCoreTaskProvider.Tasks
var mockEFCoreTaskProvider = new Mock<EFCoreTaskProvider.Tasks>(ConnectionString);
mockEFCoreTaskProvider.As<IGetContext>();
mockEFCoreTaskProvider.CallBase = true;
mockEFCoreTaskProvider.Setup(x => x.GetContext()).Returns(context);
// Mock CoreReportService.DataReports
var config = new DBConfig { DAL = "EFCore", ConnectionString = ConnectionString };
var mockDataReports = new Mock<DataReports>(config).As<IDataReports>();
mockDataReports.CallBase = true;
mockDataReports.Setup(x => x.GetTaskProvider()).Returns(mockEFCoreTaskProvider.Object);
var test = mockDataReports.Object.SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders().ToList();
Assert.IsTrue(test.Count == 1);
}
}
I'm using an inmemorydatabase to test the data but the test.Count of the TestCase has the count that corresponds from the real database.
If I make GetContext() and GetTaskProvider() virtual I'm getting the correct Count from the virtual database but I don't want them to be virtual, I also prefer if they were not public, what am I doing wrong?

This is a matter of design.
what am i doing wrong?
DataReports is tightly coupled to implementation concerns and is also violating Single Responsibility Principle (SRP) and Separation of Concerns (SoC).
By having DataReports create the providers, it is tightly coupled to them, and prevents you from being able to replace them when testing.
Abstract the provider creation out into its own concern
For example
//Abstraction
public interface ITaskProviderFactory {
ITaskEnumerableProvider GetTaskProvider();
}
//Implementation
public class DefaultTaskProviderFactory : ITaskProviderFactory{
private readonly DBConfig config;
public DefaultTaskProviderFactory(DBConfig config) {
this.config = config;
}
public ITaskEnumerableProvider GetTaskProvider() {
switch (config.DAL) {
case "ADO":
return new ADOTaskProvider.Tasks(config.ConnectionString);
case "EFCore":
return new EFCoreTaskProvider.Tasks(config.ConnectionString);
default:
throw new FormatException("The format of the variable which represent the selected DAL was not correct");
}
}
}
And refactor the DataReports accordingly
public class DataReports : IDataReports {
private readonly ITaskProviderFactory factory;
public DataReports(ITaskProviderFactory factory) {
this.factory = factory;
}
private ITaskEnumerableProvider getTaskProvider() {
return factory.GetTaskProvider();
}
public IEnumerable<SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders> SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders() {
return getTaskProvider().SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders().ToList();
}
}
At run-time in production, the appropriate implementation can be explicitly injected.
For an integration test of DataReports the actual implementations can be swapped out as needed in order to verify the expected behavior.
For example
[TestMethod]
public void SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrdersTest() {
//Arrange
var options = new DbContextOptionsBuilder<DAL_EFCore.AdventureWorks2017Context>()
.UseInMemoryDatabase(databaseName: "SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrdersTest")
.Options;
using (var context = new DAL_EFCore.AdventureWorks2017Context(options)) {
InsertData(options);
}
using (var context = new DAL_EFCore.AdventureWorks2017Context(options)) {
//actual EFCoreTaskProvider.Tasks targeting in-memory database
var taskProvider = new EFCoreTaskProvider.Tasks(context);
//mock factory configured to return the desired provider
var mockFactory = Mock.Of<ITaskProviderFactory>(_ =>
_.GetTaskProvider() == taskProvider //return the actual provider for testing
);
// actual CoreReportService.DataReports (Subject under test)
var dataReports = DataReports(mockFactory);
//Act
var result = dataReports.SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders().ToList();
//Assert
Assert.IsTrue(result.Count == 1);
}
}
The original design of your classes is not very flexible so it makes it difficult to isolate parts for testing.

Related

Writing integration tests against EF Core and a SQL Server database

Working sample at the end of this article (I kept all things I tried [reason for long article], so that others could benefit from it later)
I am trying to write integration tests for my EF Core 3.1 class library. As unit test framework, I have used XUnit and followed guide from Microsoft: https://learn.microsoft.com/en-us/ef/core/testing/sharing-databases
Here is how the setup looks like (it's a bit longer because I am actually creating a database in my SQL Server in case I need to see the real result from tests output):
public class SharedDatabaseFixture : IDisposable
{
private static readonly object _lock = new object();
private static bool _databaseInitialized;
private static string _DatabaseName = "Database.Server.Local";
private static IConfigurationRoot config;
public SharedDatabaseFixture()
{
config = new ConfigurationBuilder()
.AddJsonFile($"appsettings.Development.json", true, true)
.Build();
var test = config.GetValue<string>("DataSource");
var connectionStringBuilder = new SqlConnectionStringBuilder
{
DataSource = config.GetValue<string>("DataSource"),
InitialCatalog = _DatabaseName,
IntegratedSecurity = true,
};
var connectionString = connectionStringBuilder.ToString();
Connection = new SqlConnection(connectionString);
CreateEmptyDatabaseAndSeedData();
Connection.Open();
}
public bool ShouldSeedActualData { get; set; } = true;
public DbConnection Connection { get; set; }
public ApplicationDbContext CreateContext(DbTransaction transaction = null)
{
var identity = new GenericIdentity("admin#sample.com", "Admin");
var contextUser = new ClaimsPrincipal(identity); //add claims as needed
var httpContext = new DefaultHttpContext() { User = contextUser };
var defaultHttpContextAccessor = new HttpContextAccessor();
defaultHttpContextAccessor.HttpContext = httpContext;
var context = new ApplicationDbContext(new DbContextOptionsBuilder<ApplicationDbContext>().UseSqlServer(Connection).Options, null, defaultHttpContextAccessor);
if (transaction != null)
{
context.Database.UseTransaction(transaction);
}
return context;
}
private static void ExecuteSqlCommand(SqlConnectionStringBuilder connectionStringBuilder, string commandText)
{
using (var connection = new SqlConnection(connectionStringBuilder.ConnectionString))
{
connection.Open();
using (var command = connection.CreateCommand())
{
command.CommandText = commandText;
command.ExecuteNonQuery();
}
}
}
private static SqlConnectionStringBuilder Master => new SqlConnectionStringBuilder
{
DataSource = config.GetValue<string>("DataSource"),
InitialCatalog = "master",
IntegratedSecurity = true
};
private static string Filename => Path.Combine(Path.GetDirectoryName(typeof(SharedDatabaseFixture).GetTypeInfo().Assembly.Location), $"{_DatabaseName}.mdf");
private static string LogFilename => Path.Combine(Path.GetDirectoryName(typeof(SharedDatabaseFixture).GetTypeInfo().Assembly.Location), $"{_DatabaseName}_log.ldf");
private static void CreateDatabaseRawSQL()
{
ExecuteSqlCommand(Master, $#"IF(db_id(N'{_DatabaseName}') IS NULL) BEGIN CREATE DATABASE [{_DatabaseName}] ON (NAME = '{_DatabaseName}', FILENAME = '{Filename}') END");
}
private static List<T> ExecuteSqlQuery<T>(SqlConnectionStringBuilder connectionStringBuilder, string queryText, Func<SqlDataReader, T> read)
{
var result = new List<T>();
using (var connection = new SqlConnection(connectionStringBuilder.ConnectionString))
{
connection.Open();
using (var command = connection.CreateCommand())
{
command.CommandText = queryText;
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
result.Add(read(reader));
}
}
}
}
return result;
}
private static void DestroyDatabaseRawSQL()
{
var fileNames = ExecuteSqlQuery(Master, $#"SELECT [physical_name] FROM [sys].[master_files] WHERE [database_id] = DB_ID('{_DatabaseName}')", row => (string)row["physical_name"]);
if (fileNames.Any())
{
ExecuteSqlCommand(Master, $#"ALTER DATABASE [{_DatabaseName}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;EXEC sp_detach_db '{_DatabaseName}', 'true'");
fileNames.ForEach(File.Delete);
}
if (File.Exists(Filename))
File.Delete(Filename);
if (File.Exists(LogFilename))
File.Delete(LogFilename);
}
private void CreateEmptyDatabaseAndSeedData()
{
lock (_lock)
{
if (!_databaseInitialized)
{
using (var context = CreateContext())
{
try
{
DestroyDatabaseRawSQL();
}
catch (Exception) { }
try
{
CreateDatabaseRawSQL();
context.Database.EnsureCreated();
}
catch (Exception) { }
if (ShouldSeedActualData)
{
List<UserDB> entities = new List<UserDB>()
{
new UserDB() { Id = "Admin#sample.com", Name= "Admin" }
};
context.Users.AddRange(entities);
context.SaveChanges();
List<IdentityRole> roles = new List<IdentityRole>()
{
new IdentityRole(){Id = "ADMIN",Name = nameof(DefaultRoles.Admin), NormalizedName = nameof(DefaultRoles.Admin)},
new IdentityRole(){Id = "FINANCE",Name = nameof(DefaultRoles.Finance), NormalizedName = nameof(DefaultRoles.Finance)}
};
context.Roles.AddRange(roles);
context.SaveChanges();
}
}
_databaseInitialized = true;
}
}
}
public void Dispose()
{
Connection.Dispose();
}
}
Then, test class looks like following (for simplicity showing just 2 tests):
public class BaseRepositoryTests : IClassFixture<SharedDatabaseFixture>
{
private readonly SharedDatabaseFixture fixture;
private IMapper _mapper;
public BaseRepositoryTests(SharedDatabaseFixture fixture)
{
this.fixture = fixture;
var config = new MapperConfiguration(opts =>
{
opts.AddProfile<CountriesDBMapper>();
opts.AddProfile<EmployeeDBMapper>();
opts.AddProfile<EmployeeAccountDBMapper>();
});
_mapper = config.CreateMapper();
}
[Fact]
public async Task EntityCannotBeSavedIfDbEntityIsNotValid()
{
using (var transaction = fixture.Connection.BeginTransaction())
{
using (var context = fixture.CreateContext(transaction))
{
var baseCountryRepository = new BaseRepository<CountryDB, Country>(context, _mapper);
var invalidCountry = new Country() { };
//Act
var exception = await Assert.ThrowsAsync<DbUpdateException>(async () => await baseCountryRepository.CreateAsync(invalidCountry));
Assert.NotNull(exception.InnerException);
Assert.Contains("Cannot insert the value NULL into column", exception.InnerException.Message);
}
}
}
[Fact]
public async Task EntityCanBeSavedIfEntityIsValid()
{
using (var transaction = fixture.Connection.BeginTransaction())
{
using (var context = fixture.CreateContext(transaction))
{
var baseCountryRepository = new BaseRepository<CountryDB, Country>(context, _mapper);
var item = new Country() { Code = "SK", Name = "Slovakia" };
//Act
var result = await baseCountryRepository.CreateAsync(item);
Assert.NotNull(result);
Assert.Equal(1, result.Id);
}
}
}
}
Finally here is a sample of repository implementation (CRUD):
public async Task<TModel> CreateAsync(TModel data)
{
var newItem = mapper.Map<Tdb>(data);
var entity = await context.Set<Tdb>().AddAsync(newItem);
await context.SaveChangesAsync();
return mapper.Map<TModel>(entity.Entity);
}
public async Task<bool> DeleteAsync(long id)
{
var item = await context.Set<Tdb>().FindAsync(id).ConfigureAwait(false);
if (item == null)
throw new ArgumentNullException();
var result = context.Set<Tdb>().Remove(item);
await context.SaveChangesAsync();
return (result.State == EntityState.Deleted || result.State == EntityState.Detached);
}
If I run these tests individually, each one of them passes without a problem. However if I run all tests from BaseRepositoryTests then I am getting random problems as somehow, database transactions are not rolled back but the data is saved and shared between tests.
I have checked and truly, each transaction has it's own unique ID, thus they should not collide. Am I missing anything here? I mean according to Microsoft this is correct approach, but there is clearly something I have missed. The only thing different from other guides that I could find is, that I am using SaveChangesAsync in my repository implementation, while other's use SaveChanges...however I believe that this should not be the root cause of my problem.
Any help in respect to this matter would be highly appreciated.
Update 1:
As suggested by comments, I have tried two separate approaches. First one was to use CommitableTransaction like following:
Method update:
[Fact]
public async Task EntityCanBeSavedIfEntityIsValid()
{
using (var transaction = new CommittableTransaction(new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
{
using (var context = fixture.CreateContext(transaction))
{
var baseCountryRepository = new BaseRepository<CountryDB, Country>(context, _mapper);
var item = new Country() { Code = "SK", Name = "Slovakia" };
//Act
var result = await baseCountryRepository.CreateAsync(item);
Assert.NotNull(result);
Assert.Equal(1, result.Id);
}
}
}
Shared fixture update:
public ApplicationDbContext CreateContext(CommittableTransaction transaction = null)
{
... other code
if (transaction != null)
{
context.Database.EnlistTransaction(transaction);
}
return context;
}
This unfortunately ended with the same result when running my code tests in bulk (data that I was saving ended up being incremented and not discarded after each test)
Second thing I tried was using TransactionScope like following:
[Fact]
public async Task EntityCanBeModifiedIfEntityExistsAndIsValid()
{
using (var scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.ReadUncommitted }, TransactionScopeAsyncFlowOption.Enabled))
{
using (var context = fixture.CreateContext())
{
var baseCountryRepository = new BaseRepository<CountryDB, Country>(context, _mapper);
var item = new Country() { Code = "SK", Name = "Slovakia" };
//Act
var insertResult = await baseCountryRepository.CreateAsync(item);
Assert.NotNull(insertResult);
Assert.Equal(1, insertResult.Id);
Assert.Equal("SK", insertResult.Code);
Assert.Equal("Slovakia", insertResult.Name);
//Act
insertResult.Code = "SVK";
var result = await baseCountryRepository.UpdateAsync(insertResult.Id, insertResult);
Assert.Equal(1, result.Id);
Assert.Equal("SVK", result.Code);
Assert.Equal("Slovakia", result.Name);
}
scope.Complete();
}
}
Just as before this did not yield any new results.
Last thing that I tried was to drop :IClassFixture<SharedDatabaseFixture> from the test class and instead, create a new instance of my database fixture in constructor (which is being triggered for each test run) like following:
public BaseRepositoryTests()
{
this.fixture = new SharedDatabaseFixture();
var config = new MapperConfiguration(opts =>
{
opts.AddProfile<CountriesDBMapper>();
opts.AddProfile<EmployeeDBMapper>();
opts.AddProfile<EmployeeAccountDBMapper>();
});
_mapper = config.CreateMapper();
}
Just as before, no new results came from this update.
Working setup
Shared database fixture (basically class responsible for creating database...the main difference between previous version now is, that in constructor it accepts unique guid that is used when creating database -> to create a database with unique name. Furthermore I have also added a new method ForceDestroyDatabase() which is responsible to destroy the database after test has done it's job. I did not place it in Dispose() method, as sometimes you want to check what actually happened to database, where in that case you just don't call the method...see later)
public class SharedDatabaseFixture : IDisposable
{
private static readonly object _lock = new object();
private static bool _databaseInitialized;
private string _DatabaseName = "FercamPortal.Server.Local.";
private static IConfigurationRoot config;
public SharedDatabaseFixture(string guid)
{
config = new ConfigurationBuilder()
.AddJsonFile($"appsettings.Development.json", true, true)
.Build();
var test = config.GetValue<string>("DataSource");
this._DatabaseName += guid;
var connectionStringBuilder = new SqlConnectionStringBuilder
{
DataSource = config.GetValue<string>("DataSource"),
InitialCatalog = _DatabaseName,
IntegratedSecurity = true,
};
var connectionString = connectionStringBuilder.ToString();
Connection = new SqlConnection(connectionString);
CreateEmptyDatabaseAndSeedData();
Connection.Open();
}
...other code the same as above, skipped for clarity
private void CreateEmptyDatabaseAndSeedData()
{
lock (_lock)
{
using (var context = CreateContext())
{
try
{
DestroyDatabaseRawSQL();
}
catch (Exception ex) { }
try
{
CreateDatabaseRawSQL();
context.Database.EnsureCreated();
}
catch (Exception) { }
if (ShouldSeedActualData)
{
List<UserDB> entities = new List<UserDB>()
{
new UserDB() { Id = "Robert_Jokl#swissre.com", Name= "Robert Moq" },
new UserDB() { Id = "Test_User#swissre.com", Name= "Test User" }
};
context.Users.AddRange(entities);
context.SaveChanges();
List<IdentityRole> roles = new List<IdentityRole>()
{
new IdentityRole(){Id = "ADMIN",Name = nameof(FercamDefaultRoles.Admin), NormalizedName = nameof(FercamDefaultRoles.Admin)},
new IdentityRole(){Id = "FINANCE",Name = nameof(FercamDefaultRoles.Finance), NormalizedName = nameof(FercamDefaultRoles.Finance)}
};
context.Roles.AddRange(roles);
context.SaveChanges();
}
}
}
}
public void ForceDestroyDatabase()
{
DestroyDatabaseRawSQL();
}
public void Dispose()
{
Connection.Close();
Connection.Dispose();
}
}
Sample test class:
public class DailyTransitDBRepositoryTests : IDisposable
{
private readonly SharedDatabaseFixture fixture;
private readonly ApplicationDbContext context;
private IMapper _mapper;
public DailyTransitDBRepositoryTests()
{
this.fixture = new SharedDatabaseFixture(Guid.NewGuid().ToString("N"));
this.context = this.fixture.CreateContext();
this.context.Database.OpenConnection();
var config = new MapperConfiguration(opts =>
{
opts.AddProfile<DailyTransitDBMapper>();
opts.AddProfile<EmployeeDBMapper>();
opts.AddProfile<EmployeeAccountDBMapper>();
opts.AddProfile<CountriesDBMapper>();
});
_mapper = config.CreateMapper();
}
...other code ommited for clarity
public void Dispose()
{
this.context.Database.CloseConnection();
this.context.Dispose();
this.fixture.ForceDestroyDatabase();
this.fixture.Dispose();
}
[Fact]
public async Task GetTransitsForYearAndMonthOnlyReturnsValidItems()
{
var employees = await PopulateEmployeesAndReturnThemAsList(context);
var countries = await PopulateCountriesAndReturnThemAsList(context);
var transitRepository = new DailyTransitDBRepository(context, _mapper);
var transitItems = new List<DailyTransit>() {
new DailyTransit()
{
Country = countries.First(),
Employee = employees.First(),
Date = DateTime.Now,
TransitionDurationType = DailyTransitDurationEnum.FullDay
},
new DailyTransit()
{
Country = countries.First(),
Employee = employees.Last(),
Date = DateTime.Now.AddDays(1),
TransitionDurationType = DailyTransitDurationEnum.FullDay
},
new DailyTransit()
{
Country = countries.First(),
Employee = employees.Last(),
Date = DateTime.Now.AddMonths(1),
TransitionDurationType = DailyTransitDurationEnum.FullDay
}
};
//Act
await transitRepository.CreateRangeAsync(transitItems);
//retrieve all items
using (var context2 = fixture.CreateContext())
{
var transitRepository2 = new DailyTransitDBRepository(context2, _mapper);
var items = await transitRepository2.GetEmployeeTransitsForYearAndMonth(DateTime.Now.Year, DateTime.Now.Month);
Assert.Equal(2, items.Count());
Assert.Equal("Janko", items.First().Employee.Name);
Assert.Equal("John", items.Last().Employee.Name);
}
}
}
Robert, Glad It helped! As per your request, I re-submit the answer for anyone that could find this answer helpful as you.
I learn the hard way that trying to share the entity framework database context over IClassFixture or CollectionFixtures would eventually end up in tests being polluted with another test data or deadlock/race conditions due to the parallel execution of xUnit, entity framework throwing exceptions because it already tracked that object with a given Id and more headaches like that.
Personally, I would kindly recommend that for your specific use cause, stick the database context creation/cleanup within the constructor/dispose alternative such as:
public class TestClass : IDisposable
{
DatabaseContext DatabaseContext;
public TestClass()
{
var options = new DbContextOptionsBuilder<DatabaseContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
DatabaseContext = new DatabaseContext(options);
//insert the data that you want to be seeded for each test method:
DatabaseContext.Set<Product>().Add(new Product() { Id = 1, Name = Guid.NewGuid().ToString() });
DatabaseContext.SaveChanges();
}
[Fact]
public void FirstTest()
{
var product = DatabaseContext.Set<Product>().FirstOrDefault(x => x.Id == 1).Name;
//product evaluates to => 0f25a10b-1dfd-4b4b-a69d-4ec587fb465b
}
[Fact]
public void SecondTest()
{
var product = DatabaseContext.Set<Product>().FirstOrDefault(x => x.Id == 1).Name;
//product evaluates to => eb43d382-40a5-45d2-8da9-236d49b68c7a
//It's different from firstTest because is another object
}
public void Dispose()
{
DatabaseContext.Dispose();
}
}
Of course you can always do some refinement, but the idea is there

Unity Container With Repository and UnitOfWork

I have an ASP.NET WEB API project that using the repository and unit of work patterns along with UnityContainer. I registered all my repositories and unit of work classes with PerRequestLifeTimeManager and everything is working great. Every client request to server work in a single transaction separated from other client requests. After the request is returned to the client, the DbContext is disposed.
My problem begins when I need to to perform heavy actions after I return a response to the client. The DbContext is disposed and I can't request the Unity Container for a new instance (Since no HttpContext exist ).
I read this post PerRequestLifetimeManager can only be used in the context of an HTTP request
And tried to implement 2 different UnityContainer
WebContainer - Register all classes using PerRequestLifeTimeManager
CorContainer - Register all classes using PerResolveLifetimeManager
I thought I solved the problem but I noticed that each repository I request from the CoreContainer using a different DbContext. Also the UnitOfWork has a different DbContext. This off course causing many error in my code.
This is the code I'm using to register my entities in UnityContainer. I have 2 Databases so some of the repository using the DbContext of the first database, and the other using the second DbContext. My UnitOfWork using both DbContexts
public static class UnityConfig
{
#region Unity Container
private static Lazy<IUnityContainer> perRequestContainer =
new Lazy<IUnityContainer>(() =>
{
var container = new UnityContainer();
RegisterCommonTypes<PerRequestLifetimeManager>(container);
return container;
});
private static Lazy<IUnityContainer> perResolveContainer =
new Lazy<IUnityContainer>(() =>
{
var container = new UnityContainer();
RegisterCommonTypes<PerResolveLifetimeManager>(container);
//CNSDeployerService can
container.RegisterType<ICNSDeployerService, CNSDeployerService>(new PerResolveLifetimeManager());
return container;
});
public static IUnityContainer WebContainer => perRequestContainer.Value;
public static IUnityContainer CoreContainer => perResolveContainer.Value;
#endregion
/// <summary>
/// Please notice this configuration exist only inside the scope of a single request
/// See UnityWebApiActivator
/// </summary>
/// <param name="container"></param>
public static void RegisterCommonTypes<T>(IUnityContainer container) where T: LifetimeManager
{
container.RegisterType<ApplicationDbContext, ApplicationDbContext>(Activator.CreateInstance<T>());
container.RegisterType<DbContext, ApplicationDbContext>(Activator.CreateInstance<T>());
container.RegisterType<capNsubContext, capNsubContext>(Activator.CreateInstance<T>());
container.RegisterType<IDataContextAsync, capNsubContext>("capNsubContext", Activator.CreateInstance<T>());
container.RegisterType<IDataContextAsync, LinetServerContext>("linnetDataContext", Activator.CreateInstance<T>());
container.RegisterType<IRepository<Entity>, Repository<Entity>>(Activator.CreateInstance<T>());
container.RegisterType<IRepositoryAsync<VideoTargetLanguage>, Repository<VideoTargetLanguage>>(Activator.CreateInstance<T>());
//Unity understand array by defaults so we just need to map it to IEnumerable
container.RegisterType<IEnumerable<IDataContextAsync>, IDataContextAsync[]>();
container.RegisterType<IUnitOfWorkAsync, UnitOfWork>(Activator.CreateInstance<T>());
container.RegisterType<UserManager<ApplicationUser>>(Activator.CreateInstance<T>());
container.RegisterType<RoleManager<IdentityRole>>(Activator.CreateInstance<T>());
container.RegisterType<AccountController>(Activator.CreateInstance<T>());
container.RegisterType<IUserStore<ApplicationUser>, UserStore<ApplicationUser>>(Activator.CreateInstance<T>());
container.RegisterType<IOrderService, OrderService>(Activator.CreateInstance<T>());
container.RegisterType<IFFMpegService, FFMpegService>(Activator.CreateInstance<T>());
container.RegisterType<IVideoService, VideoService>(Activator.CreateInstance<T>());
container.RegisterType<IOrderItemService, OrderItemService>(Activator.CreateInstance<T>());
container.RegisterType<ILanguageService, LanguageService>(Activator.CreateInstance<T>());
container.RegisterType<IUserService, UserService>(Activator.CreateInstance<T>());
container.RegisterType<ICNSCaptionsService, CNSCaptionsService>(Activator.CreateInstance<T>());
container.RegisterType<ICNSTranslationsService, CNSTranslationsService>(Activator.CreateInstance<T>());
container.RegisterType<ICNSCapMoviesService, CNSMovieService>(Activator.CreateInstance<T>());
container.RegisterType<HttpClient, HttpClient>(Activator.CreateInstance<T>());
container.RegisterType<SimpleRefreshTokenProvider, SimpleRefreshTokenProvider>(Activator.CreateInstance<T>());
var capNsubEntityTypes = GetEntityFrameworkEntityTypesByContext<capNsubContext>();
var linnetEntityTypes = GetEntityFrameworkEntityTypesByContext<LinetServerContext>();
RegisterEntitiesRepostiories<T>(container, capNsubEntityTypes, "capNsubContext");
RegisterEntitiesRepostiories<T>(container, linnetEntityTypes, "linnetDataContext");
}
private static void RegisterEntitiesRepostiories<T>(IUnityContainer container, IEnumerable<Type> entities, string contextName)
where T:LifetimeManager
{
var iGenericRepositoryTypes = new[] { typeof(IRepositoryAsync<>), typeof(IRepository<>) };
foreach (var iGenericRepositoryType in iGenericRepositoryTypes)
{
foreach (var entityType in entities)
{
var iSpecificRepositoryType = iGenericRepositoryType.MakeGenericType(entityType);
var genericRepositoryType = typeof(Repository<>);
var specificRepositoryType = genericRepositoryType.MakeGenericType(entityType);
container.RegisterType(iSpecificRepositoryType, Activator.CreateInstance<T>(), new InjectionFactory(c =>
{
return Activator.CreateInstance(specificRepositoryType, c.Resolve<IDataContextAsync>(contextName), c.Resolve<IUnitOfWorkAsync>());
}));
}
}
}
private static IEnumerable<Type> GetEntityFrameworkEntityTypesByContext<T>() where T : DataContext
{
var capNsubContextType = typeof(T);
var capNsubDataAssembly = Assembly.GetAssembly(capNsubContextType);
var ef6EntityType = typeof(Repository.Pattern.Ef6.Entity);
return capNsubDataAssembly.GetTypes()
.Where(t => String.Equals(t.Namespace, capNsubContextType.Namespace, StringComparison.Ordinal) &&
t.IsSubclassOf(ef6EntityType));
}
}
[System.Web.Http.Authorize(Roles = "admin")]
[System.Web.Http.RoutePrefix("api/job")]
public class JobController : BaseApiController {
[System.Web.Http.Route("Create", Name = "Create")]
[System.Web.Http.HttpPost]
public IHttpActionResult Create(JobBindingModel createJobModal)
{
//We have to use the CoreContainer since cnsDeployer scope runs outside of the request
var cnsDeployer = UnityConfig.CoreContainer.Resolve<ICNSDeployerService>();
if (!ModelState.IsValid)
{
return BadRequest();
}
try
{
//This runs in the backround after we return the response to client
cnsDeployer.Deploy(createJobModal.ItemIds);
return Ok();
}
catch(Exception err)
{
return InternalServerError(err);
}
}
}
public class CNSDeployerService : ICNSDeployerService
{
private readonly IOrderItemService orderItemService;
private readonly ICNSCapMoviesService cnsMoviesService;
private readonly ICNSTranslationsService cnsTranslationsService;
private readonly IFFMpegService ffMpegService;
private readonly IUnitOfWorkAsync unitOfWorkAsync;
private readonly IVideoService videoService;
public CNSDeployerService(IOrderItemService orderItemService,
ICNSCapMoviesService cnsCapMoviesService,
ICNSTranslationsService cnsTranslationsService,
IFFMpegService ffMpegService,
IUnitOfWorkAsync unitOfWorkAsync,
IVideoService videoService)
{
this.orderItemService = orderItemService;
this.cnsMoviesService = cnsCapMoviesService;
this.cnsTranslationsService = cnsTranslationsService;
this.ffMpegService = ffMpegService;
this.unitOfWorkAsync = unitOfWorkAsync;
this.videoService = videoService;
}
public void Deploy(IEnumerable<Guid> orderItemIds)
{
try
{
InnerDeploy(orderItemIds);
}
catch
{
unitOfWorkAsync.Dispose();
}
}
private void InnerDeploy(IEnumerable<Guid> orderItemIds)
{
var orderItems = orderItemService.Queryable()
.Where(orderItem => orderItemIds.Any(itemId => orderItem.Id == itemId)
&& orderItem.IsInProcessQueue == false
&& !orderItem.TranslationId.HasValue)
.ToList();
if (orderItems.Count == 0)
{
unitOfWorkAsync.Dispose();
throw new ArgumentNullException("No valid orders was provided");
}
foreach ( var orderItem in orderItems)
{
orderItem.IsInProcessQueue = true;
orderItemService.Update(orderItem);
}
unitOfWorkAsync.SaveChanges();
var translationId = Guid.NewGuid();
var movieId = Guid.NewGuid();
var connectedMoviePath = cnsMoviesService.GetMoviePath(movieId);
var videosUrlList = orderItems
.Select(orderItem => orderItem.VideosTable.VideoUrl)
.ToList();
//Don't await on this task since we want concat to continue after request is returned
Task.Run(async () =>
{
try
{
await ffMpegService.ConcatVideos(videosUrlList, connectedMoviePath);
VideoUtils.CreateVideoImageAndReturnPath(connectedMoviePath);
var videosTotalDuration = videoService.GetVideosTotalDuration(orderItemIds);
var durationInSeconds = Convert.ToInt32((int)(videosTotalDuration / 1000));
await cnsMoviesService.CreateMovieRecordAsync(movieId, durationInSeconds);
await cnsTranslationsService.CreateTranslationRecordAsync(movieId, translationId, language: 1);
var index = 0;
foreach (var orderItem in orderItems)
{
orderItem.TranslationId = translationId;
orderItem.TranslationIndex = index++;
orderItem.IsInProcessQueue = false;
orderItemService.Update(orderItem);
}
await unitOfWorkAsync.SaveChangesAsync();
}
catch (Exception err)
{
//TODO: Handle error
}
finally
{
//Dispose db context
unitOfWorkAsync.Dispose();
}
});
}
}

Mock a controller for unit test without any service reference C#

I am having a simple controller which needs to be unit tested not integration tested. I just need a way to mock so that I can verify if receive method is called. We already have test against Receive(), so no need to verify what is going inside that method.
My code looks like
public class MessageController : Controller
{
private readonly ConnectionDetail connectionDetail;
private readonly QueueDetail queueDetail;
public MessageController(IOptions<ConnectionDetail> connectionDetail, IOptions<QueueDetail> queueDetail)
{
this.connectionDetail = connectionDetail.Value;
this.queueDetail = queueDetail.Value;
}
[HttpGet()]
public IActionResult Get()
{
try
{
var channel = CreateConnectionAndChannel(queueDetail);
var message = channel.Receive();
var hbaseKey = new HbaseKey { Key = new Guid(message) };
return Ok(hbaseKey);
}
catch
{
return StatusCode(500, "Exception occured while processing. Try again.");
}
}
private IChannel CreateConnectionAndChannel(QueueDetail queueDetail)
{
var factory = new Factory();
var adapter = factory.Connect(MessagingType.MQ, connectionDetail);
return adapter.BindQueue(queueDetail);
}
}
Refactor the CreateConnectionAndChannel function out into its own service
public interface IChannelProvider {
IChannel CreateConnectionAndChannel();
}
and have controller explicitly depend on that service
public class MessageController : Controller {
private readonly IChannelProvider channelProvider;
public MessageController(IChannelProvider channelProvider) {
this.channelProvider = channelProvider;
}
[HttpGet()]
public IActionResult Get() {
try {
var channel = channelProvider.CreateConnectionAndChannel();
var message = channel.Receive();
var hbaseKey = new HbaseKey { Key = new Guid(message) };
return Ok(hbaseKey);
} catch {
return StatusCode(500, "Exception occured while processing. Try again.");
}
}
}
So now only the IChannelProvider needs to be mocked to test the controller in isolation.
I just need a way to mock so that I can verify if receive method is called.
public void Verify_Received_Called() {
//Arrange
var channel = new Mock<IChannel>();
channel
.Setup(_ => _.Receive())
.Returns("My mock value here");
var mockProvider = new Mock<IChannelProvider>();
mockProvider.Setup(_ => _.CreateConnectionAndChannel())
.Returns(channel.Object);
var controller = new MessageController(mockProvider.Object);
//Act
var result = controller.Get();
//Assert
channel.Verify(_ => _.Receive(), Times.AtLeastOnce);
}
The provider implementation could look like...
public class ChannelProvider : IChannelProvider {
private readonly ConnectionDetail connectionDetail;
private readonly QueueDetail queueDetail;
public ChannelProvider(IOptions<ConnectionDetail> connectionDetail, IOptions<QueueDetail> queueDetail) {
this.connectionDetail = connectionDetail.Value;
this.queueDetail = queueDetail.Value;
}
public IChannel CreateConnectionAndChannel() {
var factory = new Factory();
var adapter = factory.Connect(MessagingType.MQ, connectionDetail);
return adapter.BindQueue(queueDetail);
}
}
In order to do this, you need to move your CreateConnectionAndChannel method to a separate dependency, for instance, ChannelFactory which implements IChannelFactory interface.
public interface IChannelFactory {
IChannel CreateConnectionAndChannel(QueueDetail queueDetail);
}
public class ChannelFactory : IChannelFactory {
public IChannel CreateConnectionAndChannel(QueueDetail queueDetail)
{
var factory = new Factory();
var adapter = factory.Connect(MessagingType.MQ, connectionDetail);
return adapter.BindQueue(queueDetail);
}
}
public class MessageController : Controller
{
private readonly ConnectionDetail connectionDetail;
private readonly QueueDetail queueDetail;
private readonly IChannelFactory channelFactory;
public MessageController(IOptions<ConnectionDetail> connectionDetail, IOptions<QueueDetail> queueDetail, IChannelFactory channelFactory)
{
this.connectionDetail = connectionDetail.Value;
this.queueDetail = queueDetail.Value;
this.channelFactory = channelFactory;
}
[HttpGet()]
public IActionResult Get()
{
try
{
var channel = channelFactory.CreateConnectionAndChannel(queueDetail);
var message = channel.Receive();
var hbaseKey = new HbaseKey { Key = new Guid(message) };
return Ok(hbaseKey);
}
catch
{
return StatusCode(500, "Exception occured while processing. Try again.");
}
}
}
After that you can mock your controller in test (using Moq for example):
[TestFixture]
public class TestMessageController
{
[Test]
public void TestGet()
{
var channelMock = new Mock<IChannel>(MockBehavior.Strict);
channelMock
.Setup(c => c.Receive())
.Returns(null);
var channelFactoryMock = new Mock<IChannelFactory>(MockBehavior.Strict);
channelFactory
.Setup(cf => cf.CreateConnectionAndChannel(It.IsAny<IOptions<QueueDetail>>()))
.Returns();
var controller = new MessageController(null, null, channelFactoryMock.Object);
controller.Get();
}
}

Setting up mock objects for EF dbcontext to test repository methods

I have entityframework repo which gets trip information from sqldb. I have created the repo and on constructor injecting the dbContext and using that context doing database operation.
public class WorldRepository : IWorldRepository
{
private WorldContext _context;
public WorldRepository(WorldContext context)
{
_context = context;
}
public void AddSop(string tripName, Stop newStop)
{
var trip = GetTipByName(tripName);
if (trip != null)
{
trip.Stops.Add(newStop);
_context.Stops.Add(newStop);
}
}
public void AddTrip(Trip trip)
{
_context.Add(trip);
}
public IEnumerable<Trip> GetAllTrips()
{
return _context.Trips.ToList();
}
}
Now I am trying to test using MOQ, but this does not help . I am not able to test any of logic writen on my methods as it's querying mock object and not my implementation.
// private Mock<IWorldRepository> _mockWorld;
[TestMethod]
public void Test_AddTrips()
{
//Arrange
// WorldRepository repo = new WorldRepository(null);
Mock<IWorldRepository> _mockWorld = new Mock<IWorldRepository>();
var repo = _mockWorld.Object;
//Act
repo.AddSop("Sydney", new Stop
{
Arrival = DateTime.Now,
Id = 2,
Latittude = 0.01,
Longitude = 0.005,
Name = "Test Trip",
Order = 5
});
repo.SaveChangesAsync();
var count = repo.GetAllTrips().Count();
//Assert
Assert.AreEqual(1, count);
}
This is code for WorldContext.
public class WorldContext:DbContext
{
private IConfigurationRoot _config;
public WorldContext(IConfigurationRoot config,DbContextOptions options)
:base(options)
{
_config = config;
}
public DbSet<Trip> Trips { get; set; }
public DbSet<Stop> Stops{ get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder.UseSqlServer(_config["ConnectionStrings:WorldCotextConnection"]);
}
}
I am assuming that you are trying to mock WorldContextand to use it with your repo instance ,so we need to mock that first. To do that create an interface for worlddbcontext.
public interface IWorldContext
{
DbSet<Stop> Stops { get; set; }
DbSet<Trip> Trips { get; set; }
}
Now what you want is to mock the dependencies and the test the subject.
In this case you want to mock WorldDbContext , mock the DbSet<Stop> and test AddSop method.
To create a mock DbSet, I am refering to MSDN, EF Testing with Mocking framework as also mentioned by Jasen in the comments.
private Mock<IWorldContext> _context;
private WorldRepository _repo;
[TestMethod]
public void Test_AddTrips()
{
////Arrange
var data = new List<Stop> {
new Stop
{
Arrival = DateTime.Now.AddDays(-15),
Id = 1,
Latittude = 0.05,
Longitude = 0.004,
Name = "Test Trip01",
Order = 1
},
new Stop
{
Arrival = DateTime.Now.AddDays(-20),
Id = 2,
Latittude = 0.07,
Longitude = 0.015,
Name = "Test Trip02",
Order = 2
}
}.AsQueryable();
var mockSet = new Mock<DbSet<Stop>>();
mockSet.As<IQueryable<Stop>>().Setup(m => m.Provider).Returns(data.Provider);
mockSet.As<IQueryable<Stop>>().Setup(m => m.Expression).Returns(data.Expression);
mockSet.As<IQueryable<Stop>>().Setup(m => m.ElementType).Returns(data.ElementType);
mockSet.As<IQueryable<Stop>>().Setup(m => m.GetEnumerator()).Returns( data.GetEnumerator());
_context = new Mock<IWorldContext>();
//Set the context of mock object to the data we created.
_context.Setup(c => c.Stops).Returns(mockSet.Object);
//Create instance of WorldRepository by injecting mock DbContext we created
_repo = new WorldRepository(_context.Object);
//Act
_repo.AddSop("Sydney",
new Stop
{
Arrival = DateTime.Now,
Id = 2,
Latittude = 0.01,
Longitude = 0.005,
Name = "Test Trip",
Order = 5
});
_repo.SaveChangesAsync();
var count = _repo.GetAllTrips().Count();
//Assert
Assert.AreEqual(3, count);
}
Also , there is a brilliant module (just a reference, no endorsement or anything else) on plural sight by Mosh on Testing Repository module he has explained this in great detail.
If you want to test WorldRepository you need to create real object of this type and mock all it's external dependencies - WorldContext in your case.
So proper setup for your test should be like this:
var contextMock = new Mock<WorldContext>();
contextMock.Setup(...); //setup desired behaviour
var repo = new WorldRepository(contextMock.Object);

Can't get Moq Setup to not return null

I'm trying to Setup a Mock call to a the IModelFactory interface that I have.
here is the IModelFactory interface
public interface IModelFactory
{
MaterialAcceptedModel Create(MaterialAccepted model);
MaterialAccepted Parse(MaterialAcceptedModel model);
}
This is the ModelFactory class which implements the IModelFactor interface (I've only add here the method I'm trying to test, no need to add the implementation of the Create Method)
public class ModelFactory : IModelFactory
{
private UrlHelper _urlHelper;
private IRTWRepository _repo;
//private IKeysGeneratorService _keysGen;
private IGeocodeService _geocoder;
public ModelFactory(HttpRequestMessage request, IRTWRepository repo,IGeocodeService geocoder)
{
_urlHelper = new UrlHelper(request);
_repo = repo;
_geocoder = geocoder;
}
#region Parses
public MaterialAccepted Parse(MaterialAcceptedModel model)
{
try
{
if (!string.IsNullOrWhiteSpace(model.category))
{
var category = _repo.CategoryRepository.Get(model.category);
if (category == null) return null;
var entry = new MaterialAccepted()
{
business = model.business,
businessService = model.businessService,
residential = model.residential,
residentialService = model.residentialService,
note = model.note,
Category = category
};
return entry;
}
return null;
}
catch
{
return null;
}
}
#endregion
}
I'm using a BaseAPiController that contains the repo and configuration Interfaces
public class BaseApiController : ApiController
{
IRTWRepository _repository;
IModelFactory _modelFactory;
IConfiguration _configuration;
IGeocodeService _geocoder;
public BaseApiController(IRTWRepository repository,IConfiguration configuration)
{
_repository = repository;
_configuration = configuration;
}
protected IRTWRepository TheRepository
{
get
{
return _repository;
}
}
protected IConfiguration TheConfiguration
{
get
{
return _configuration;
}
}
protected IModelFactory TheModelFactory
{
get
{
_geocoder = new GeocodeService(_configuration.GetValue("geocodeGoogleApiKey"));
if (_modelFactory == null)
{
_modelFactory = new ModelFactory(this.Request, _repository,_geocoder);
}
return _modelFactory;
}
}
Here is the action method in the controller I'm trying to test
[HttpPost]
[Route("api/recyclecenters/{rcid}/materials/")]
public IHttpActionResult Post(int rcid, [FromBody]MaterialAcceptedModel model)
{
try
{
if (model != null)
{
var recycleCenter = TheRepository.RecycleCenterRepository.Get(rcid);
if (recycleCenter == null)
return NotFound();
if (!ModelState.IsValid)
return BadRequest(ModelState);
var entity = TheModelFactory.Parse(model);
if (entity == null) return BadRequest("Could not read material accepted in body");
if (TheRepository.MaterialAcceptedRepository.Get(recycleCenter.RecycleCenterId, entity.Category.name) != null)
return Conflict();
recycleCenter.Materials.Add(entity);
if (TheRepository.SaveAll())
{
string locationHeader = Url.Link("Materials", new { rcid = rcid, name = model.category.ToLower() });
return Created<MaterialAcceptedModel>(locationHeader, TheModelFactory.Create(entity));
}
return BadRequest("Could not save to the database");
}
return BadRequest();
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}
This is the line that returns null even though I mocked it up on my test method
var entity = TheModelFactory.Parse(model);
and this one is my TestClass
namespace API.Tests.Web
{
[TestClass]
public class MaterialsControllerTest
{
private Mock<IRTWRepository> repository;
private Mock<IModelFactory> factory;
private Mock<IConfiguration> configuration;
private Mock<IRTWAPIIdentityService> identityService;
private MaterialsController controller;
RecycleCenter recycleCenter;
private MaterialAccepted CreateMaterial()
{
return new MaterialAccepted()
{
business = true,
businessService = EnumRecycleCenterService.Dropoff,
residential = false,
residentialService = EnumRecycleCenterService.Pickup,
note = "this a note",
Category = new Category()
{
name = "Books"
}
};
}
[TestInitialize]
public void Initialize()
{
repository = new Mock<IRTWRepository>();
factory = new Mock<IModelFactory>();
configuration = new Mock<IConfiguration>();
identityService = new Mock<IRTWAPIIdentityService>();
controller = new MaterialsController(repository.Object,configuration.Object);
controller.Request = new HttpRequestMessage();
recycleCenter = new RecycleCenter(){RecycleCenterId = 1};
}
[TestMethod]
public void Post_ShouldReturnConflictIfTheRecycleCenterAlreadyTakesMaterial()
{
//arrange
repository.Setup(r => r.RecycleCenterRepository.Get(It.IsAny<int>())).Returns(() => recycleCenter);
factory.Setup(f => f.Parse(new MaterialAcceptedModel())).Returns(() => new MaterialAccepted());
configuration.Setup(c => c.GetValue(It.IsAny<string>())).Returns(() => "APIKEY");
repository.Setup(r => r.MaterialAcceptedRepository.Get(It.IsAny<int>(), It.IsAny<string>())).Returns(() => null);
//act
var actionResult = controller.Post(It.IsAny<int>(),new MaterialAcceptedModel());
//assert
Assert.IsInstanceOfType(actionResult, typeof(ConflictResult));
}
}
}
This is the line that's not working because it always return null instead of a new instance of MaterialAccepted
factory.Setup(f => f.Parse(new MaterialAcceptedModel())).Returns(() => new MaterialAccepted());
I tried f.Parse(It.IsAny()) but still doesn't work.
To clarify
the above line of code is returning null because is not mocking the f.Parse() call, instead is executing it and returning null because the if condition I have on that method
Anyone could explain why the Setup is not working?
Setting up your Mock using It.IsAny will work:
factory.Setup(f => f.Parse(It.IsAny<MaterialAcceptedModel>()))
.Returns(() => new MaterialAccepted());
However, as has been said by #Macilquham, I can't see where your are passing the Mock to your controller in the supplied code so that it is used by the production code.
If you don't call the method on your Mock object, which you don't, you're currently calling the method on the instance of the real object created by your base class, then it doesn't matter how you set up your mock it's not going to work. If you are able to change your base class, then doing something like this would allow you to work around your problem:
// Add defaulted parameter to base class to allow modelFactory creation
// to be overridden/injected (this will prevent the current default behaviour
// when fetching the factory, if a non-null is passed in)
public BaseApiController(IRTWRepository repository,IConfiguration configuration,
IModelFactory modelFactory = null)
{
_modelFactory = modelFactory;
}
Modify your sut constructor to allow you to supply a modelFactory (again, default it to null), then amend your test as appropriate:
controller = new MaterialsController(repository.Object,configuration.Object,
factory.Object);
You don't seem to be injecting in the IModelFactory into the controller. You need to make sure that your production code is using the Mock you are setting up in the test.
Mock cannot return null directly.
The trick is just to create a null object.
Assuming the object returned is of type class Material:
Material nullMaterial = null;
...
repository.Setup(r => r.MaterialAcceptedRepository
.Get(It.IsAny<int>(), It.IsAny<string>()))
.Returns(nullMaterial);
This should solve your problem

Categories