I am writing test for Web API application written in .NET CORE 3.1. I am using xUnit, AutoFixture & Moq for testing. I have a class that creates a new school instance in the database using Entity Framework/ DbContext. My question is how to mock dbContext & save changes, further my School DataModel has one: many relationships with SchoolBranch DataModel. I have followed this tutorial https://learn.microsoft.com/en-us/ef/ef6/fundamentals/testing/mocking
Error
Message:
Moq.MockException :
Expected invocation on the mock once, but was 0 times: m => m.Add<School>(It.IsAny<School>())
Performed invocations:
Mock<SchoolDbContext:1> (m):
No invocations performed.
Stack Trace:
Mock.Verify(Mock mock, LambdaExpression expression, Times times, String failMessage)
Mock`1.Verify[TResult](Expression`1 expression, Times times)
CreateSchoolCommandTest.ExecuteMethod_ShouldReturnNewGuidId_IfSuccess() line 50
School
public class School
{
public School()
{
this.SchoolBranches = new HashSet<SchoolBranch>();
}
public Guid SchoolID { get; set; }
public string Name { get; set; }
public ICollection<SchoolBranch> SchoolBranches { get; set; }
}
SchoolBranch
public class SchoolBranch
{
public SchoolBranch()
{
}
public Guid SchoolBranchID { get; set; }
public Guid SchoolID { get; set; }
public string Address { get; set; }
public int PhoneNumber { get; set; }
public School School { get; set; }
}
CreateSchool Class
public class CreateSchool : BaseCommand<Guid>, ICreateSchool
{
public SchoolDto SchoolDtos { get; set; }
public CreateSchool(IAppAmbientState appAmbient) : base(appAmbient) { }
public override Guid Execute()
{
try
{
var schoolId = Guid.NewGuid();
List<SchoolBranch> schoolBranches = new List<SchoolBranch>();
foreach(var item in SchoolDtos.SchoolBranchDtos)
{
schoolBranches.Add(new SchoolBranch()
{
SchoolBranchID = Guid.NewGuid(),
SchoolID = schoolId,
Address = item.Address,
PhoneNumber = item.PhoneNumber
});
}
var school = new School()
{
SchoolID = schoolId,
Name = SchoolDtos.Name,
SchoolBranches = schoolBranches
};
schoolDbContext.Schools.Add(school);
schoolDbContext.SaveChanges();
return school.SchoolID;
}
catch(Exception exp)
{
appAmbientState.Logger.LogError(exp);
throw;
}
}
}
Test Class
public class CreateSchoolCommandTest
{
private readonly ICreateSchool sut;
private readonly Mock<IAppAmbientState> appAmbientState = new Mock<IAppAmbientState>();
[Fact]
public void ExecuteMethod_ShouldReturnNewGuidId_IfSuccess()
{
//Arrange
var fixture = new Fixture();
var schoolDtoMock = fixture.Create<SchoolDto>();
var schoolDbSetMock = new Mock<DbSet<School>>();
var schoolBranchDbSetMock = new Mock<DbSet<SchoolBranch>>();
var schoolDbContextMock = new Mock<SchoolDbContext>();
//schoolDbSetMock.Setup(x => x.Add(It.IsAny<School>())).Returns((School s) => s); // this also did not work
schoolDbContextMock.Setup(m => m.Schools).Returns(schoolDbSetMock.Object);
//Act
sut.SchoolDtos = schoolDtoMock;
var actualDataResult = sut.Execute();
// Assert
Assert.IsType<Guid>(actualDataResult);
schoolDbContextMock.Verify(m => m.Add(It.IsAny<School>()), Times.Once());
schoolDbContextMock.Verify(m => m.SaveChanges(), Times.Once());
}
BaseCommand (DbContext is created here)
public abstract class BaseCommand<T>
{
protected SchoolDbContext schoolDbContext;
protected IAppAmbientState appAmbientState { get; }
public BaseCommand(IAppAmbientState ambientState)
{
this.schoolDbContext = new SchoolDbContext();
this.appAmbientState = ambientState;
}
public abstract T Execute();
}
For fix Error
You made just a little mistake. Insted of
schoolDbContextMock.Verify(m => m.Add(It.IsAny<School>()), Times.Once());
schoolDbContextMock.Verify(m => m.SaveChanges(), Times.Once());
You should have
schoolDbSetMock.Verify(m => m.Add(It.IsAny<School>()), Times.Once());
schoolDbContextMock.Verify(m => m.SaveChanges(), Times.Once());
Because you use method Add() on schoolDbContext.Schools not on schoolDbContext
For injecting dbContext
Your BaseCoommand class constructor should look like this:
public BaseCommand(IAppAmbientState ambientState, SchoolDbContext schoolDbContext)
{
this.schoolDbContext = schoolDbContext;
this.appAmbientState = ambientState;
}
Your CreateSchool class constructor:
public CreateSchool(IAppAmbientState appAmbient, SchoolDbContext schoolDbContext) : base(appAmbient, schoolDbContext) { }
And next in test you should initialize CreateSchool in test like this:
var sut = new CreateSchool(ambientState, schoolDbContextMock.Object);
And it will work
I have a method in my repository that I’m trying to test
public User UpdateUserManyToMany(User user, List<Guid> manyToManyds)
{
var dbContext = _databaseContext as DbContext;
dbContext?.TryUpdateManyToMany(user.ManyToMany, manyToManyds
.Select(x => new ManyToMany{
OtherEntityId = x,
UserId = user.Id,
}), x => x.OtherEntityId);
return user;
}
My ManyToMany Entity :
public class ManyToMany
{
public Guid OtherEntityId { get; set; }
public OtherEntity OtherEntityId { get; set; }
public Guid UserId { get; set; }
public User User { get; set; }
}
My TryUpdateManyToMany :
public static class ManyToManyExtensions
{
public static void TryUpdateManyToMany<T, TKey>(this DbContext db, IEnumerable<T> currentItems, IEnumerable<T> newItems, Func<T, TKey> getKey) where T : class
{
db.Set<T>().RemoveRange(currentItems.Except(newItems, getKey));
db.Set<T>().AddRange(newItems.Except(currentItems, getKey));
}
public static IEnumerable<T> Except<T, TKey>(this IEnumerable<T> items, IEnumerable<T> other, Func<T, TKey> getKeyFunc)
{
return items
.GroupJoin(other, getKeyFunc, getKeyFunc, (item, tempItems) => new { item, tempItems })
.SelectMany(t => t.tempItems.DefaultIfEmpty(), (t, temp) => new { t, temp })
.Where(t => ReferenceEquals(null, t.temp) || t.temp.Equals(default(T)))
.Select(t => t.t.item);
}
}
Here’s my unit test :
using (var context = new InMemoryDataBaseContext())
{
// Arrange
var repository = new UserRepository(context);
await context.Users.AddRangeAsync(GetUser());
await context.SaveChangesAsync();
// Act
var manyIds = new List<Guid>();
manyIds.Add(Guid.Parse("855d1a64-a707-40d5-ab93-34591a923abf"));
manyIds.Add(Guid.Parse("855d1a64-a787-40d9-ac93-34591a923abf"));
manyIds.Add(Guid.Parse("855d1a64-a707-41d9-ab93-39591a923abf"));
var user = new User();
var expected = repository.UpdateUserManyToMany(GetUser(), manyIds);
// Assert
}
But I get the following error in my test :
Message:
System.InvalidOperationException : The instance of entity type 'ManyToMany' cannot be tracked because another instance with the same key value for {'UserId', 'OtherEntityId'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.
Arborescence des appels de procédure:
IdentityMap`1.ThrowIdentityConflict(InternalEntityEntry entry)
IdentityMap`1.Add(TKey key, InternalEntityEntry entry, Boolean updateDuplicate)
IdentityMap`1.Add(TKey key, InternalEntityEntry entry)
NullableKeyIdentityMap`1.Add(InternalEntityEntry entry)
StateManager.StartTracking(InternalEntityEntry entry)
InternalEntityEntry.SetEntityState(EntityState oldState, EntityState newState, Boolean acceptChanges, Boolean modifyProperties)
InternalEntityEntry.SetEntityState(EntityState entityState, Boolean acceptChanges, Boolean modifyProperties, Nullable`1 forceStateWhenUnknownKey)
EntityGraphAttacher.PaintAction(EntityEntryGraphNode`1 node)
EntityEntryGraphIterator.TraverseGraph[TState](EntityEntryGraphNode`1 node, Func`2 handleNode)
EntityGraphAttacher.AttachGraph(InternalEntityEntry rootEntry, EntityState targetState, EntityState storeGeneratedWithKeySetTargetState, Boolean forceStateWhenUnknownKey)
DbContext.SetEntityState(InternalEntityEntry entry, EntityState entityState)
DbContext.RemoveRange(IEnumerable`1 entities)
InternalDbSet`1.RemoveRange(IEnumerable`1 entities)
ManyToManyExtensions.TryUpdateManyToMany[T,TKey](DbContext db, IEnumerable`1 currentItems, IEnumerable`1 newItems, Func`2 getKey) ligne 24
UserRepository.UpdateUserManyToMany(User user, List`1 manyToManyds) ligne 59
MyRepoUnitTest.MyTestMethod() ligne 102
--- End of stack trace from previous location where exception was thrown ```
The following sample program, that is based on the code you provided, works without issues:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace IssueConsoleTemplate
{
public class User
{
public Guid Id { get; set; }
public string Name { get; set; }
public ICollection<ManyToMany> ManyToMany { get; set; } = new HashSet<ManyToMany>();
}
public class OtherEntity
{
public Guid Id { get; set; }
public string Name { get; set; }
public ICollection<ManyToMany> ManyToMany { get; set; } = new HashSet<ManyToMany>();
}
public class ManyToMany
{
public Guid OtherEntityId { get; set; }
public Guid UserId { get; set; }
public OtherEntity OtherEntity { get; set; }
public User User { get; set; }
}
public class Context : DbContext
{
public DbSet<OtherEntity> OtherEntities { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<ManyToMany> ManyToMany { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer(#"Data Source=.\MSSQL14;Integrated Security=SSPI;Initial Catalog=So63077461")
.UseLoggerFactory(
LoggerFactory.Create(
b => b
.AddConsole()
.AddFilter(level => level >= LogLevel.Information)))
.EnableSensitiveDataLogging()
.EnableDetailedErrors();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<OtherEntity>(
entity =>
{
entity.HasKey(e => e.Id);
entity.HasMany(e => e.ManyToMany)
.WithOne(e => e.OtherEntity)
.HasForeignKey(e => e.OtherEntityId);
entity.HasData(
new OtherEntity
{
Id = new Guid("855d1a64-a707-40d5-ab93-34591a923abf"),
Name = "Bicycle"
},
new OtherEntity
{
Id = new Guid("855d1a64-a787-40d9-ac93-34591a923abf"),
Name = "Bus"
},
new OtherEntity
{
Id = new Guid("855d1a64-a707-41d9-ab93-39591a923abf"),
Name = "Plane"
});
});
modelBuilder.Entity<User>(
entity =>
{
entity.HasKey(e => e.Id);
entity.HasMany(e => e.ManyToMany)
.WithOne(e => e.User)
.HasForeignKey(e => e.UserId);
});
modelBuilder.Entity<ManyToMany>(
entity =>
{
entity.HasKey(e => new {e.OtherEntityId, e.UserId});
});
}
}
public static class ManyToManyExtensions
{
public static void TryUpdateManyToMany<T, TKey>(this DbContext db, IEnumerable<T> currentItems, IEnumerable<T> newItems, Func<T, TKey> getKey) where T : class
{
db.Set<T>().RemoveRange(currentItems.Except(newItems, getKey));
db.Set<T>().AddRange(newItems.Except(currentItems, getKey));
}
public static IEnumerable<T> Except<T, TKey>(this IEnumerable<T> items, IEnumerable<T> other, Func<T, TKey> getKeyFunc)
{
return items
.GroupJoin(other, getKeyFunc, getKeyFunc, (item, tempItems) => new { item, tempItems })
.SelectMany(t => t.tempItems.DefaultIfEmpty(), (t, temp) => new { t, temp })
.Where(t => ReferenceEquals(null, t.temp) || t.temp.Equals(default(T)))
.Select(t => t.t.item);
}
}
internal class UserRepository
{
private readonly Context _databaseContext;
public UserRepository(Context context)
{
_databaseContext = context;
}
public User UpdateUserManyToMany(User user, List<Guid> manyToManyds)
{
var dbContext = _databaseContext as DbContext;
dbContext?.TryUpdateManyToMany(user.ManyToMany, manyToManyds
.Select(x => new ManyToMany{
OtherEntityId = x,
UserId = user.Id,
}), x => x.OtherEntityId);
return user;
}
}
internal static class Program
{
private static async Task Main()
{
//
// Operations with referential integrity intact:
//
using var context = new Context();
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
// Arrange
var repository = new UserRepository(context);
await context.Users.AddRangeAsync(GetUser());
await context.SaveChangesAsync();
// Act
var manyIds = new List<Guid>
{
new Guid("855d1a64-a707-40d5-ab93-34591a923abf"),
new Guid("855d1a64-a787-40d9-ac93-34591a923abf"),
new Guid("855d1a64-a707-41d9-ab93-39591a923abf")
};
var expected = repository.UpdateUserManyToMany(GetUser(), manyIds);
}
private static User GetUser()
=> User;
private static readonly User User = new User
{
Id = new Guid("30c35d2e-77fd-480b-9974-6ebf037a8f86"),
Name = "John"
};
}
}
System.InvalidOperationException : The instance of entity type 'ManyToMany' cannot be tracked because another instance with the same key value for {'UserId', 'OtherEntityId'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.
The error message says, that at least one of your ManyToMany entries already exists in the database (there is an entry with the same UserId and OtherEntityId combination).
You can verify this, by running the following code directly after your filled the manyIds variable with the 3 IDs:
var user = GetUser();
var alreadyExistingManyToMany = context.ManyToMany
.Where(m => m.UserId == user.Id &&
manyIds.Contains(m.OtherEntityId))
.ToList();
Debug.Assert(alreadyExistingManyToMany.Count == 0);
I'm wanting to change a request to be GET instead of POST so users can share the generated URLs.
The current system uses a normal form submit and takes advantage of the automatic serialization between the form submit and the MVC ActionResult for a List of custom objects.
e.g.
<form action="/MyPage">
<input type="hidden" id="MyThings_0__Value" name="MyThings[0].Value">
<input type="hidden" id="MyThings_0__Flag" name="MyThings[0].Flag">
<input type="hidden" id="MyThings_1__Value" name="MyThings[1].Value">
<input type="hidden" id="MyThings_1__Flag" name="MyThings[1].Flag">
</form>
However doing it this way causes the GET string generated to be overly long and complicated. This is bad because the MyThings list can be up to 10 items long.
http://myurl.com/MyPage?MyThings%5B0%5D.Value=ThisIsValue1&MyThings%5B0%5D.Flag=1&MyThings%5B1%5D.Value=ThisIsValue2&MyThings%5B1%5D.Flag=2
I was hoping for the string to appear more user-friendly. Something like:
http://myurl.com/MyPage?MyThings=ThisIsValue1-1,ThisIsValue2-2
Can this be done with custom serialization? And if so, how would I go about implementing it?
My Model and ActionResult:
namespace MyNamespace {
public class MyThing {
public string Value { get; set; }
public int Flag { get; set; }
}
public class Filter {
public string CustomAttribute1 { get; set; }
public string CustomAttribute2 { get; set; }
public string CustomAttribute3 { get; set; }
public List<MyThing> MyThings { get; set; } = new List<MyThing>();
}
public ActionResult MyPage(Filter filter) {
MyModel model = StaticMethod.GetMyModel(filter);
return View(model);
}
}
In the end I decided against using custom URL serialization and used helper methods to convert to string/class backward and forwards within the C# model.
public class Filter {
public string MyThing { get; set; }
public List<MyThingClass> MyThings {
get {
if (this._myThings == null) { // Default to Query string
this._myThings = ToQueryList(this.MyThing);
}
return this._myThings;
}
set {
this.MyThing = ToQueryString(value); // Automatically assign QueryString to serialized QueryItems on set
this._myThings = value;
}
}
private List<MyThingClass> _myThings { get; set; }
public static List<MyThingClass> ToQueryList(string queryString) {
return queryString.Split(',').Select(x => MyThingClass.FromString(x)).ToList();
}
public static string ToQueryString(List<MyThingClass> myThings) {
return string.Join(",", myThings.Select(x => x.ToString()));
}
}
public class MyThingClass {
public string Value { get; set; }
public int Flag { get; set; }
/// <summary>Converts a QueryItem object to a serialized object ready for the QueryString.</summary>
public override string ToString() {
return string.Concat(this.Value, "-", this.Flag);
}
public static MyThingClass FromString(string value) {
var v = value.Split('-');
return new MyThingClass() {
Value = v[0],
Flag = Convert.ToInt32(v[1])
};
}
}
I need to display a different list each time I click on different links in my view. Help would be appreciated :)
My controller:
public class HomeController : Controller
{
Teams tm = new Teams();
Details det = new Details();
public ActionResult Index()
{
var model = new List<Teams>();
model.Add(new Teams { Name = "Manchester United", NickName = "The Red Devils", HomeGround = "Old Trafford", Founded = 1878 });
model.Add(new Teams { Name = "Liverpool", NickName = "The reds", HomeGround = "Anfield", Founded = 1870 });
return View(model);
}
public ActionResult About()
{
var title = new List<Details>();
title.Add(new Details { MajorHonours = 62, PremLeague = 20, FACup = 11, LeagueCup = 4, UEFA = 3 });
title.Add(new Details { MajorHonours = 60, PremLeague = 18, FACup = 7, LeagueCup = 8, UEFA = 5 });
return View();
}
My view with the links:
#model IEnumerable<Standings.Models.Teams>
#{
ViewBag.Title = "Standings";
}
<h1>List of teams</h1>
#foreach (var item in Model)
{
<div>
#Html.ActionLink(#item.Name, "About") (#item.NickName, #item.HomeGround, #item.Founded)
<hr />
</div>
}
My model:
public class Details
{
public int MajorHonours { get; set; }
public int PremLeague { get; set; }
public int FACup { get; set; }
public int LeagueCup { get; set; }
public int UEFA { get; set; }
}
And I have a clean View with the name About that the list needs to be displayed on
I have a Apicontroller Which has dependency on unit of work object. How to write a test case for mocking ApiController Which has dependency on unit of work implemented in ApiController constructor.
Here is the code:
ApiController:
public class UserController : ApiController
{
public IUoW UoW { get; set; }
// GET api/user
public UserController(IUoW uow)
{
UoW = uow;
}
public IEnumerable<Users> Get()
{
return UoW.Users.Getall();
}
}
The Test case :
[TestMethod]
public void TestApiController()
{
var userManager = new Mock<IUoW>();
userManager.Setup(s => s.Users);
var controller = new UserController(userManager.Object);
var values = controller.Get();
Assert.IsNotNull(values);
}
The Users Class which has been mentioned here in UoW.Users is
public class UoW:IUoW,IDisposable
{
private MvcWebApiContext DbContext { get; set; }
protected IRepositoryProvider RepositoryProvider { get; set; }
private IRepository<T> GetStandardRepo<T>() where T : class
{
return RepositoryProvider.GetRepositoryForEntityType<T>();
}
public IRepository<Users> Users
{
get { return GetStandardRepo<Users>(); }
}
}
and the Users class itself is
[Table("UserProfile")]
public class Users
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
[DataType(DataType.PhoneNumber)]
public long Mobile { get; set; }
}
When I am trying to debug the test case , the Test case shows the object UoW.Users is null in UserController. Its obvious its not initializing through WebActivator since global.asax isnt invoked here through mock. Now how to write a successful test scenario in this context so that the WebApicontroller returns me the users object with data ?
Are you using Moq?
As I cannot see what type the UoW.Users property is I cannot demo how to mock it properly (updated IRepository) but that should be mocked and the GetAll method stubbed to return a sample list of users.
Updated
var userManager = new Mock<IUoW>();
userManager.Setup(s => s.Users).Returns(()=>
{
var userReposisitory = new Mock<IRepository<Users>>();
userReposisitory.Setup(ur => ur.GetAll()).Returns(()=> {
var listOfUsers = new List<Users>();
listOfUsers.Add(new Users { FirstName = "Example" });
return listOfUsers.AsQueryable();
});
return userReposisitory.Object;
});
var controller = new UserController(userManager.Object);
var result = controller.Get();
Assert.IsNotNull(result);
Assert.IsTrue(result.Count() > 0);