Error while creating plugin in Nop Commerce - asp.net-mvc

This is my first demo project in Nopcommerce and i have tried to make my own plugin but during the time of Build some error is seen. Below are some codes.
namespace Nop.Plugin.Aowi.Testimonial.Data
{
public class TestimonialRecordObjectContext : DbContext , IDbContext
{
public TestimonialRecordObjectContext(string nameOrConnectionString) : base(nameOrConnectionString) { }
#region Implementation of IDbContext
#endregion
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new TestimonialRecordMap());
base.OnModelCreating(modelBuilder);
}
public string CreateDatabaseInstallationScript()
{
return ((IObjectContextAdapter)this).ObjectContext.CreateDatabaseScript();
}
public void Install()
{
//It's required to set initializer to null (for SQL Server Compact).
//otherwise, you'll get something like "The model backing the 'your context name' context has changed since the database was created. Consider using Code First Migrations to update the database"
Database.SetInitializer<TestimonialRecordObjectContext>(null);
Database.ExecuteSqlCommand(CreateDatabaseInstallationScript());
SaveChanges();
}
public void Uninstall()
{
var dbScript = "DROP TABLE Testimonial";
Database.ExecuteSqlCommand(dbScript);
SaveChanges();
}
public new IDbSet<TEntity> Set<TEntity>() where TEntity : BaseEntity
{
return base.Set<TEntity>();
}
public System.Collections.Generic.IList<TEntity> ExecuteStoredProcedureList<TEntity>(string commandText, params object[] parameters) where TEntity : BaseEntity, new()
{
throw new System.NotImplementedException();
}
public System.Collections.Generic.IEnumerable<TElement> SqlQuery<TElement>(string sql, params object[] parameters)
{
throw new System.NotImplementedException();
}
public int ExecuteSqlCommand(string sql, bool doNotEnsureTransaction = false, int? timeout = null, params object[] parameters)
{
throw new System.NotImplementedException();
}
}
}
This is the Dependency registrar part
namespace Nop.Plugin.Aowi.Testimonial.Infastructure
{
public class DependencyRegistrar: IDependencyRegistrar
{
private const string CONTEXT_NAME ="nop_object_context_product_view_tracker";
public virtual void Register(ContainerBuilder builder, ITypeFinder typeFinder, NopConfig config)
{
//data context
this.RegisterPluginDataContext<TestimonialRecordObjectContext>(builder, CONTEXT_NAME);
//override required repository with our custom context
builder.RegisterType<EfRepository<TestimonialRecord>>()
.As<IRepository<TestimonialRecord>>()
.WithParameter(ResolvedParameter.ForNamed<IDbContext>(CONTEXT_NAME))
.InstancePerLifetimeScope();
}
public int Order
{
get { return 1; }
}
}
}
Even after cleaning and Building i am getting this error.
Can anyone help me with this. I have done all of this by watching a tutorial so if anyone can help me correct my mistake i will be really greatful.

You just need to implement this method and properties of IDbContext interface, which are described in error log, in your custom context.
for example, how it is done in one of the existing plugin Tax.CountryStateZip:
public void Detach(object entity)
{
if (entity == null)
throw new ArgumentNullException("entity");
((IObjectContextAdapter)this).ObjectContext.Detach(entity);
}
public virtual bool ProxyCreationEnabled
{
get { return this.Configuration.ProxyCreationEnabled; }
set { this.Configuration.ProxyCreationEnabled = value; }
}
public virtual bool AutoDetectChangesEnabled
{
get { return this.Configuration.AutoDetectChangesEnabled; }
set { this.Configuration.AutoDetectChangesEnabled = value; }
}

I cannot get an idea from your code that where is actual issue. But i suggest by an example.
make your install method code like:
public void Install()
{
//create the table
var dbScript = CreateDatabaseScript();
Database.ExecuteSqlCommand(dbScript);
SaveChanges();
}
Add a new class called EfStartUpTask and paste following code:
public class EfStartUpTask : IStartupTask
{
public void Execute()
{
//It's required to set initializer to null (for SQL Server Compact).
//otherwise, you'll get something like "The model backing the 'your context name' context has changed since the database was created. Consider using Code First Migrations to update the database"
Database.SetInitializer<YourContext>(null);
}
public int Order
{
//ensure that this task is run first
get { return 0; }
}
}
And your DependencyRegistrar :
public class DependencyRegistrar : IDependencyRegistrar
{
public virtual void Register(ContainerBuilder builder, ITypeFinder typeFinder)
{
builder.RegisterType<YourService>().As<YourserviceInterface>().InstancePerLifetimeScope();
//data context
this.RegisterPluginDataContext<YourContext>(builder, "nop_object_context_product_view_tracker");
//override required repository with our custom context
builder.RegisterType<EfRepository<YourEntityClass>>()
.As<IRepository<YourEntityClass>>()
.WithParameter(ResolvedParameter.ForNamed<IDbContext>("nop_object_context_product_view_tracker"))
.InstancePerLifetimeScope();
}
public int Order
{
get { return 1; }
}
}
Note: you have to change YourContext to your context name and same as for entity class
Hope this helps!

Related

Issue in Connecting Web App with localdb using DataContext file and webConfig file

I have created MVC web application using Repository & DI approach. I have used Code First approach too.
Here is my DataContext file:
namespace EfRepPatTest.Data
{
public class DataContext : DbContext, IDbContext
{
public new IDbSet<TEntity> Set<TEntity>() where TEntity: class
{
return base.Set<TEntity>();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
var typesToRegister = Assembly.GetExecutingAssembly().GetTypes()
.Where(type => !String.IsNullOrEmpty(type.Namespace))
.Where(type => type.BaseType != null && type.BaseType.IsGenericType &&
type.BaseType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration<>));
foreach (var type in typesToRegister)
{
dynamic configurationInstance = Activator.CreateInstance(type);
modelBuilder.Configurations.Add(configurationInstance);
}
base.OnModelCreating(modelBuilder);
}
}
}
I have defined connecting string in Web.Config file like below:
<add name="DataContext"
connectionString="Data Source=(LocalDB)\v11.0;AttachDbFilename=|DataDirectory|\eCommerce.mdf;Integrated Security=True"
providerName="System.Data.SqlClient"
/>
Please note here I have mentioned same name to the Connection string and my context file.
Here is my post method:
[HttpPost]
public ActionResult Create(CategoryModel model)//FormCollection collection
{
try
{
// TODO: Add insert logic here
if (model == null)
return View(model);
var category = new Category();
category.Name = model.Name;
categoryService.Insert(category);
return RedirectToAction("Index");
}
catch
{
return View(model);
}
}
CategoryService:
public class CategoryService : ICategoryService
{
private IRepository<Category> _categoryRepository;
public CategoryService(IRepository<Category> categoryRepository)
{
this._categoryRepository = categoryRepository;
}
public void Insert(Category category)
{
if (category == null)
throw new ArgumentNullException("Category");
_categoryRepository.Insert(category);
}
}
RepositoryService:
public class RepositoryService<TEntity> : IRepository<TEntity> where TEntity: class
{
private IDbContext _context;
private IDbSet<TEntity> Entities
{
get { return this._context.Set<TEntity>(); }
}
public RepositoryService(IDbContext context)
{
this._context = context;
}
public void Insert(TEntity entity)
{
Entities.Add(entity);
}
}
When I run application on the first time it will create local db. But when I going to insert data, I did not get any error from the application and it does not insert my data to the DB.
What cause this? What I have done wrong here?
Any help is appreciated!
You should call SaveChanges() on _context after all changes like this for ex.:
public void Insert(TEntity entity)
{
Entities.Add(entity);
_context.SaveChanges();
}

AutoMapper+xUnit: Missing type map configuration or unsupported mapping

I cannot figure this one out. I have a N-Tier ASP.MVC application and I am writing my first Unit Test and it seems to fail on my AutoMapper configuration. I have used AutoMapper a million times and never had any problems using it.
I'm sure I am missing something simple, but I have been staring at this for 24 hours now.
Class Library: APP.DOMAIN
public class User : IEntity<int>
{
public int Id { get; set; }
[StringLength(20), Required]
public string UserName { get; set; }
}
Class Library: APP.SERVICE
References App.Domain
public class UserViewModel
{
public int Id { get; set; }
public string UserName { get; set; }
}
I have my AutoMapper bootstrapper in the service layer.
public static class AutoMapperBootstrapper
{
public static void RegisterMappings()
{
Mapper.CreateMap<User, UserViewModel>();
}
}
UserService.cs
public class UserService : IUserService
{
private readonly IUserRepository _userRepository;
public UserService(IUserRepository userRepository)
{
_userRepository = userRepository;
}
public List<UserViewModel> GetUsers()
{
var users = _userRepository.GetAll();
if (users == null)
{
throw new Exception("No users found.");
}
return Mapper.Map<List<UserViewModel>>(users); // FAILS ON AUTOMAPPER
}
}
ASP.MVC Layer: APP.WEB
References App.Service
private void Application_Start(object sender, EventArgs e)
{
// Register AutoMapper
AutoMapperBootstrapper.RegisterMappings();
Mapper.AssertConfigurationIsValid();
// Code that runs on application startup
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
Unit Test Layer:
public class TestUserRepository :IUserRepository
{
public IEnumerable<User> GetAll()
{
var users = new List<User>()
{
new User { Id = 1, UserName = "Mary"},
new User { Id = 2, UserName = "Joe"}
};
return users;
}
}
public class UserServiceTest
{
private IUserService _userService;
private readonly IUserRepository _userRepository;
public UserServiceTest()
{
_userRepository = new TestUserRepository();
}
[Fact]
public void GetUsers_Should_Return_Correct_Number_Of_Users()
{
// Arrange
_userService = new UserService(_userRepository);
// Act
var result = _userService.GetUsers(); // FAILS ON AUTOMAPPER
// Assert
Assert.True(result.Any(u => u.UserName == "Mary"));
}
}
Failing Test Message:
*** Failures ***
Exception
AutoMapper.AutoMapperMappingException: AutoMapper.AutoMapperMappingException : Missing type map configuration or unsupported mapping.
Mapping types:
User -> UserViewModel
App.Data.Model.User -> App.Service.ViewModels.UserViewModel
Destination path:
List`1[0]
Source value:
App.Data.Model.User
at App.Service.Services.UserService.GetUsers() in D:\Repositories\App\App.Service\Services\UserService.cs:line 36
at App.Tests.Service.Tests.UserServiceTest.GetUsers_Should_Return_Correct_Number_Of_Users() in D:\Repositories\App\App.Tests\Service.Tests\UserServiceTest.cs:line 34
A little late to the party but have you tried setting the mapping before running the test?
public class UserServiceTest
{
public UserServiceTest()
{
// register the mappings before running the test
AutoMapperBootstrapper.RegisterMappings();
}
...
}
What we would need to do is Inject Custom Mapper Mock as given below. Add all those custom profiles that you have used for that particular class that you are unit testing and inject ConfigureMapper() in the Constructor of that class which is expecting IMapper Object
public IMapper ConfigureMapper()
{
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfile<CustomProfile>();
cfg.AddProfile<UserCustomProfile>();
cfg.AddProfile<UserWorkProfile>();
});
return config.CreateMapper();
}
Hope this solves the issue.
I'm not sure what the problem is, it's been a while since I've last used AutoMapper, but I'm quite sure that the following will work:
return users.Select(Mapper.Map<UserViewModel>);
I have a problem with this line:
var authorDTO = mapper.Map<AuthorCreationDTO>(AuthorinsideDB);
So I change the version of Autormapper
from:
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="7.0.0" />
to
Version="6.0.0"
and it worked.

controller post actionresult not saving changes to database

I have a post method in my controller that is not saving changes to my database (SQL express). I am using viewmodels and valueinjector to populate the VM from my model. I have checked and the values in the viewmodel and they have changed, but when I call my service:
fixedAssetService.SaveFixedAsset()
and bookmark the following in the service interface:
unitOfWork.Commit()
and pull up the quick watch window for unitOfWork, it has the old value.
All my tables have primary keys and I am using code first. The connection string is valid becasue I can get the items, I just can't save them.
My post method:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(FixedAssetViewModel evm)
{
var fixedAsset = fixedAssetService.GetFixedAsset(evm.FixedAssetId);
// Use Injector to handle mapping between viewmodel and model
fixedAsset.InjectFrom(evm);
try
{
if (ModelState.IsValid)
{
fixedAssetService.SaveFixedAsset();
return RedirectToAction("Details", "FixedAsset", new { id = fixedAsset.FixedAssetId });
}
}
catch (DataException)
{
//Log the error (add a variable name after DataException)
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
}
}
My Service:
namespace FixedAssets.Services
{
public interface IFixedAssetService
{
IEnumerable<FixedAsset> GetAll();
IEnumerable<FixedAsset> FindBy(Expression<Func<FixedAsset, bool>> predicate);
FixedAsset GetFixedAsset(string id);
void CreateFixedAsset(FixedAsset fixedAsset);
void DeleteFixedAsset(string id);
void SaveFixedAsset();
bool ValueInUse(Expression<Func<FixedAsset, bool>> predicate);
}
public class FixedAssetService : IFixedAssetService
{
private readonly IFixedAssetRepository fixedAssetRepository;
private readonly IUnitOfWork unitOfWork;
public FixedAssetService(IFixedAssetRepository fixedAssetRepository, IUnitOfWork unitOfWork)
{
this.fixedAssetRepository = fixedAssetRepository;
this.unitOfWork = unitOfWork;
}
#region IFixedAssetService Members
public IEnumerable<FixedAsset> GetAll()
{
var fixedAssets = fixedAssetRepository.GetAll();
return fixedAssets;
}
public IEnumerable<FixedAsset> FindBy(Expression<Func<FixedAsset, bool>> predicate)
{
IEnumerable<FixedAsset> query = fixedAssetRepository.FindBy(predicate);
return query;
}
public bool ValueInUse(Expression<Func<FixedAsset, bool>> predicate)
{
IQueryable<FixedAsset> query = fixedAssetRepository.FindBy(predicate).AsQueryable();
int count = query.Count();
return count > 0 ? true : false;
}
public FixedAsset GetFixedAsset(string id)
{
var fixedAsset = fixedAssetRepository.GetById(id);
return fixedAsset;
}
public void CreateFixedAsset(FixedAsset fixedAsset)
{
fixedAssetRepository.Add(fixedAsset);
SaveFixedAsset();
}
public void DeleteFixedAsset(string id)
{
var fixedAsset = fixedAssetRepository.GetById(id);
fixedAssetRepository.Delete(fixedAsset);
SaveFixedAsset();
}
public void SaveFixedAsset()
{
unitOfWork.Commit();
}
#endregion
}
}
Edit: One thing I forgot to mention is this app was modeled almost exactly after an existing app that worked fine. Not sure if I have references messed up or what, but the other app uses the same methods only different entities
I found my problem. In the app I used as a model for this one I was using a separate unity class. My database factory registration was like this:
.RegisterType<IDatabaseFactory, DatabaseFactory>(new HttpContextLifetimeManager<IDatabaseFactory>())
Now I am using Microsoft.Practices.Unity and Unity.Mvc4 so I changed the registration to:
container.RegisterType<IDatabaseFactory, DatabaseFactory>();
per the comments in the bootstrapper class. When I changed it to:
container.RegisterType<IDatabaseFactory, DatabaseFactory>(new HierarchicalLifetimeManager());
per the suggestions on this post:
Stackoverflow thread
it finally worked!

Ninject Conditional Self bind to change scope (For Task-scheduler) not working properly?

Within MVC Web Application DbContext binding work properly with InRequestScope()
kernel.Bind<DbContext>().ToSelf().InRequestScope();
kernel.Bind<IUnitOfWork<DbContext>>().To<UnitOfWork<DbContext>>();
But from a Task Scheduler call DbContext in InRequestScope() unable to update Db Table (without any error), until I change Binding to InSingletonScope() OR InThreadScope()
Question: So is their any way change scope to InSingletonScope() / InThreadScope() for a Task Scheduler Call. ?
// For Task Scheduler Call, I tried bellow bind, but not working properly
kernel.Bind<DbContext>().ToSelf()
.When(request => request.Target.Type.Namespace.StartsWith("NameSpace.ClassName"))
.InSingletonScope();
** And probably I miss some thing. Need help.
Code Snippet Updated
#region Commented Code
public EmailTask() : this
( DependencyResolver.Current.GetService<IMessageManager>(),
, DependencyResolver.Current.GetService<IUnitOfWork<DbContext>>()) { }
#endregion
public EmailTask(IMessageManager messageManager, IUnitOfWork<DbContext> unitOfWork)
{
this._messageManager = messageManager;
this._unitOfWork = unitOfWork;
ProcessEmail();
}
public class NonRequestScopedParameter : IParameter { ... }
public void ProcessEmail()
{
var temp = SomeRepository.GetAll();
SendEmail(temp);
temp.Date = DateTime.Now;
SomeRepository.Update(temp);
unitOfWork.Commit();
}
public class ExecuteEmailTask : ITask
{
private readonly IResolutionRoot _resolutionRoot;
private int _maxTries = 5;
public ExecuteEmailTask(IResolutionRoot resolutionRoot)
{
_resolutionRoot = resolutionRoot;
}
public void Execute(XmlNode node)
{
XmlAttribute attribute1 = node.Attributes["maxTries"];
if (attribute1 != null && !String.IsNullOrEmpty(attribute1.Value))
{
this._maxTries = int.Parse(attribute1.Value);
}
/// send email messages
var task = _resolutionRoot.Get<EmailTask>(new NonRequestScopedParameter());
}
}
In Web.Config
<ScheduleTasks>
<Thread seconds="60">
<task name="ExecuteEmailTask" type="namespace.ExecuteEmailTask, AssemblyName" enabled="true" stopOnError="false" maxTries="5"/>
</Thread>
</ScheduleTasks>
In Global.asax
protected void Application_Start()
{
/* intialize Task */
TaskConfig.Init();
TaskManager.Instance.Initialize(TaskConfig.ScheduleTasks);
TaskManager.Instance.Start();
}
Ninject Bind Syntax
kernel.Bind<DbContext>().ToSelf().InRequestScope(); // Default bind
kernel.Bind<DbContext>().ToSelf()
.When(x => x.Parameters.OfType<NonRequestScopedParameter>().Any())
.InCallScope(); // For Scheduler
Note: EmailTask class also have SomeReposity as a Constructor Argument.
Queries:-
But what is the bind syntax to resolve TaskScheduler(IResolutionRoot resolutionRoot) ?
What is the configuration code to run TaskScheduler ?
As say to put IFakeDbContext directly into constructor, can this work with IUnitOfWork<FakeDbContext> ?
Problem
Task unable to call with Overloaded Constructor , it is only able to call TaskScheduler default Constructor.
Question 4: Can any way to invoke TaskScheduler(IResolutionRoot resolutionRoot) from TaskScheduler default constructor ?
Sample Code Snippet to create Task & run using System.Threading.Timer
private ITask createTask()
{
if (this.Enabled && (this._task == null))
{
if (this._taskType != null)
{
this._task = Activator.CreateInstance(this._taskType) as ITask;
}
this._enabled = this._task != null;
}
return this._task;
}
Question 5: Can I resolve TaskScheduler(IResolutionRoot resolutionRoot) here ?
Solved
public ExecuteEmailTask() :
this(DependencyResolver.Current.GetService<IResolutionRoot>())
OR
public ExecuteEmailTask() : this(new Bootstrapper().Kernel) { }
public ExecuteEmailTask(IResolutionRoot resolutionRoot)
{
_resolutionRoot = resolutionRoot;
}
First of, you should note that InSingletonScope() is usually a bad idea for DbContext's/Sessions. What happens if some other service changes data in the meantime? I would recommend investigating what effects this has.
For the scenario you first described, a correctly formulated .When(...) should work.
As an alternative to the .When(...) binding you could also use a .Named("FooBar") binding.
The constructor of the scheduled task would then need to look like:
ctor(Named["FooBar"] DbContext dbContext);
However, note, that this only (easily) works in case you need to inject the DbContext into a single constructor. If the task features dependencies and these need the same DbContext instance, too, it gets a bit tricker.
Since you updated your answer and say that this is the case, i would recommend an entirely different approach: Using a request parameter as basis for the When(...) condition combined with InCallScope binding. See below for an example.
Brace yourself, this is ab it of code :) The implementation requires the ninject.extensions.NamedScope extension (nuget).
I've also used xUnit and FluentAssertions nuget packages to execute the tests.
public class Test
{
// the two implementations are just for demonstration and easy verification purposes. You will only use one DbContext type.
public interface IFakeDbContext { }
public class RequestScopeDbContext : IFakeDbContext { }
public class CallScopeDbContext : IFakeDbContext { }
public class SomeTask
{
public IFakeDbContext FakeDbContext { get; set; }
public Dependency1 Dependency1 { get; set; }
public Dependency2 Dependency2 { get; set; }
public SomeTask(IFakeDbContext fakeDbContext, Dependency1 dependency1, Dependency2 dependency2)
{
FakeDbContext = fakeDbContext;
Dependency1 = dependency1;
Dependency2 = dependency2;
}
}
public class Dependency1
{
public IFakeDbContext FakeDbContext { get; set; }
public Dependency1(IFakeDbContext fakeDbContext)
{
FakeDbContext = fakeDbContext;
}
}
public class Dependency2
{
public IFakeDbContext FakeDbContext { get; set; }
public Dependency2(IFakeDbContext fakeDbContext)
{
FakeDbContext = fakeDbContext;
}
}
public class TaskScheduler
{
private readonly IResolutionRoot _resolutionRoot;
public TaskScheduler(IResolutionRoot resolutionRoot)
{
_resolutionRoot = resolutionRoot;
}
public SomeTask CreateScheduledTaskNow()
{
return _resolutionRoot.Get<SomeTask>(new NonRequestScopedParameter());
}
}
public class NonRequestScopedParameter : Ninject.Parameters.IParameter
{
public bool Equals(IParameter other)
{
if (other == null)
{
return false;
}
return other is NonRequestScopedParameter;
}
public object GetValue(IContext context, ITarget target)
{
throw new NotSupportedException("this parameter does not provide a value");
}
public string Name
{
get { return typeof(NonRequestScopedParameter).Name; }
}
// this is very important
public bool ShouldInherit
{
get { return true; }
}
}
[Fact]
public void FactMethodName()
{
var kernel = new StandardKernel();
// this is the default binding
kernel.Bind<IFakeDbContext>().To<RequestScopeDbContext>();
// this binding is _only_ used when the request contains a NonRequestScopedParameter
// in call scope means, that all objects built in the a single request get the same instance
kernel.Bind<IFakeDbContext>().To<CallScopeDbContext>()
.When(x => x.Parameters.OfType<NonRequestScopedParameter>().Any())
.InCallScope();
// let's try it out!
var task = kernel.Get<SomeTask>(new NonRequestScopedParameter());
// verify that the correct binding was used
task.FakeDbContext.Should().BeOfType<CallScopeDbContext>();
// verify that all children of the task get injected the same task instance
task.FakeDbContext.Should()
.Be(task.Dependency1.FakeDbContext)
.And.Be(task.Dependency2.FakeDbContext);
}
}
Since, as you say, the task scheduler does not make use of the IoC to create the task, it only supports a parameterless constructor. In that case you can make use DependencyResolver.Current (however, note that i'm in no way an expert on asp.net /MVC so i'm not making any claims that this is thread safe or working 100% reliably):
public class TaskExecutor : ITask
{
public TaskExecutor()
: this(DependencyResolver.Current.GetService<IResolutionRoot>())
{}
internal TaskExecutor(IResolutionRoot resolutionRoot)
{
this.resolutionRoot = resolutionRoot;
}
public void Execute()
{
IFooTask actualTask = this.resolution.Get<IFooTask>(new NonRequestScopedParameter());
actualTask.Execute();
}
}

Testing mocked EF context, context and unit of work with RhinoMocks and NUnit

I am having real problems mocking my code to enable me to test my MVC controllers.
My repository implements the following interface
public interface IEntityRepository<T>
{
IQueryable<T> All { get; }
IQueryable<T> AllIncluding(params Expression<Func<T, object>>[] includeProperties);
void Delete(int id);
T Find(int id);
void InsertOrUpdate(T entity);
void InsertOrUpdateGraph(T entity);
}
Like so
public interface IMonkeyRepository : IEntityRepository<Monkey>
{
}
My EF context implements the following interface
public interface IMonkeyContext
{
IDbSet<Monkey> Monkeys { get; set; }
DbEntityEntry Entry(object entity);
IEnumerable<DbEntityValidationResult> GetValidationErrors();
int SaveChanges();
}
My unit of work interface is defined like so
public interface IUnitOfWork<TContext> : IDisposable
{
TContext Context { get; }
int Save();
}
And implemented
public class MonkeyUnitOfWork : IUnitOfWork<IMonkeyContext>
{
private readonly IMonkeyContext context;
private bool disposed;
public MonkeyUnitOfWork(IMonkeyContext context)
{
this.context = context;
}
public IMonkeyContext Context
{
get
{
return this.context;
}
}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
public int Save()
{
var ret = this.context.SaveChanges();
return ret;
}
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
((DbContext)this.context).Dispose();
}
}
this.disposed = true;
}
}
I have a MonkeyController whos Create action I wish to test. I is defined
if (this.ModelState.IsValid)
{
this.repo.InsertOrUpdate(Mapper.Map<MonkeyModel, Monkey>(monkey));
this.uow.Save();
return this.RedirectToAction(MVC.Monkey.Index());
}
return this.View(monkey);
In my unit test I am using RhinoMocks and have defined the test
[TestFixture]
public class MonkeyControllerTests
{
MockRepository mocks = null;
private IMonkeyRepository monkeyRepository;
private IMonkeyContext context;
private MonkeyUnitOfWork unitOfWork;
private MonkeyController controller;
[SetUp]
public virtual void SetUp()
{
TestHelpers.SetupAutoMap();
this.monkeyRepository = this.Mocks.StrictMultiMock<IMonkeyRepository>(typeof(IEntityRepository<Monkey>));
this.context = this.Mocks.StrictMock<IMonkeyContext>();
this.unitOfWork = new MonkeyUnitOfWork(this.context);
this.controller = new MonkeyController(this.MonkeyRepository, this.unitOfWork);
}
[TearDown]
public virtual void TearDown()
{
if (this.mocks != null)
{
try
{
this.mocks.ReplayAll();
this.mocks.VerifyAll();
}
finally
{
this.mocks = null;
}
}
}
public MockRepository Mocks
{
get
{
if (mocks == null)
mocks = new MockRepository();
return mocks;
}
}
[Test]
public void MonkeyCreateShouldShouldDoSomeStuff()
{
var monkeyModel = ViewModelTestHelpers.CreateSingleMonkey();
var monkey = Mapper.Map<MonkeyModel, Monkey>(monkeyModel);
this.monkeyRepository.Expect(action => action.InsertOrUpdate(monkey));
this.context.Expect(action => action.SaveChanges()).Return(1);
var result = (RedirectToRouteResult)this.controller.Create(monkeyModel);
Assert.AreEqual(MVC.Monnkey.ActionNames.Index, result.RouteValues["action"]);
}
}
When I run my tests I either get the following errror
Previous method 'IMonkeyContext.SaveChanges();' requires a return value or an exception to throw.
Or it complains that the IEntityRepository.InsertOrUpdate expected 1 actual 0
I have tried so many casts, and incantations to get this to work but I am stumped. Does anyone know how to mock these object correctly? Or if I have fundamentaly missed something here?
Well it would seem to be a schoolboy error
RhinoMocks was correct when it said that IEntityRepository.InsertOrUpdate was not being called.
the line of code where I map from view model to model in my test
var monkey = Mapper.Map<MonkeyModel, Monkey>(monkeyModel);
and then use it in the expect
this.monkeyRepository.Expect(action => action.InsertOrUpdate(monkey));
was of course telling Rhino that the function should be called with this exact instance of monkey.
The function is of course called in the following way within the action
this.repo.InsertOrUpdate(Mapper.Map<MonkeyModel, Monkey>(monkey));
Not the same instance.
I have moved to the aaa syntax now and changed the test code to
this.monkeyRepository.Stub(r => r.InsertOrUpdate(Arg<Monkey>.Is.Anything));
and assert
this.monkeyRepository.AssertWasCalled(r => r.InsertOrUpdate(Arg<Monkey>.Is.Anything));
I will now go and hang my head in shame.

Resources