I have some classes that receive DbContexts thru dependency injection, that i would like to test. I am using AutoMapper's ProjectTo, as my entities are often much bigger than the objects (dto) I am returning from my class. I really like having AutoMapper adjust my query so that it only selects fields which are in my DTOs.
I've been trying to mock my DbContext, using Moq.EntityFrameworkCore. It works relatively well but it does cause issues with AutoMapper ProjectTo(). I end up getting InvalidCastException.
Obviously, I am not interested in "testing AutoMapper" or my DbContext, I just want to test my code which is around. However, I can't test my code since it crashes on the Projections.
Here's a minimalist repro, using AutoFixture to shorten the code a bit, I've thrown everything into a single file so that it's easy for anyone to try out for themselves:
using AutoFixture;
using AutoFixture.AutoMoq;
using AutoMapper;
using Microsoft.EntityFrameworkCore;
using Moq;
using Moq.EntityFrameworkCore;
using System.Collections.Generic;
using System.Threading.Tasks;
using Xunit;
namespace UnitTestEFMoqProjectTo
{
public class MyBusinessFixture
{
private IFixture _fixture;
public MyBusinessFixture()
{
_fixture = new Fixture()
.Customize(new AutoMoqCustomization());
var mockMapper = new MapperConfiguration(cfg =>
{
cfg.AddProfile(new MappingProfile());
});
var mapper = mockMapper.CreateMapper();
_fixture.Register(() => mapper);
}
[Fact]
public async Task DoSomething_WithMocksAndProjectTo_ValidatesMyLogic()
{
// Arrange
var mockContext = new Mock<MyDbContext>();
mockContext.Setup(x => x.MyEntities).ReturnsDbSet(new List<MyEntity>(_fixture.CreateMany<MyEntity>(10)));
_fixture.Register(() => mockContext.Object);
var business = _fixture.Create<MyBusiness>();
// Act
await business.DoSomething();
// Assert
Assert.True(true);
}
}
public class MyDbContext : DbContext
{
public virtual DbSet<MyEntity> MyEntities { get; set; }
}
public class MyEntity
{
public int Id { get; set; }
public string Name { get; set; }
public string SomeProperty { get; set; }
public string SomeOtherProperty { get; set; }
}
public class MyDto
{
public int Id { get; set; }
public string Name { get; set; }
}
public interface IMyBusiness
{
Task DoSomething();
}
public class MyBusiness : IMyBusiness
{
private readonly MyDbContext _myDbContext;
private readonly IMapper _mapper;
public MyBusiness(MyDbContext myDbContext, IMapper mapper)
{
_myDbContext = myDbContext;
_mapper = mapper;
}
public async Task DoSomething()
{
// My program's logic here, that I want to test.
// Query projections and enumeration
var projectedEntities = await _mapper.ProjectTo<MyDto>(_myDbContext.MyEntities).ToListAsync();
// Some more of my program's logic here, that I want to test.
}
}
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<MyEntity, MyDto>();
}
}
}
Should output the following error :
Message:
System.InvalidCastException : Unable to cast object of type 'Moq.EntityFrameworkCore.DbAsyncQueryProvider.InMemoryAsyncEnumerable`1[UnitTestEFMoqProjectTo.MyEntity]' to type 'System.Linq.IQueryable`1[UnitTestEFMoqProjectTo.MyDto]'.
Stack Trace:
ProjectionExpression.ToCore[TResult](Object parameters, IEnumerable`1 memberPathsToExpand)
ProjectionExpression.To[TResult](Object parameters, Expression`1[] membersToExpand)
Extensions.ProjectTo[TDestination](IQueryable source, IConfigurationProvider configuration, Object parameters, Expression`1[] membersToExpand)
Mapper.ProjectTo[TDestination](IQueryable source, Object parameters, Expression`1[] membersToExpand)
MyBusiness.DoSomething() line 79
MyBusinessFixture.DoSomething_WithMocksAndProjectTo_ShouldMap() line 39
Any idea of how I could keep doing projections with AutoMapper but also have unit tests working ?
For reference, here is my project file content :
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AutoFixture.AutoMoq" Version="4.11.0" />
<PackageReference Include="AutoMapper" Version="9.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.5" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
<PackageReference Include="Moq" Version="4.14.1" />
<PackageReference Include="Moq.EntityFrameworkCore" Version="3.1.2.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
I bumped into the similar issue the other day but finally I was able to run my unit tests.
I use MockQueryable.Moq (https://github.com/romantitov/MockQueryable) for mocking DBSet, probably, you should try this package because an issue might be here.
So my code looks like it:
public class Project
{
public int Id { get; set; }
public string Name { get; set; }
}
public class ProjectDto
{
public int Id { get; set; }
public string Name { get; set; }
}
class GetProjectsHandler : IRequestHandler<GetProjectsRequest, GetProjectsResponse>
{
private readonly IMyDbContext context;
private readonly IMapper mapper;
public GetProjectsHandler(IMyDbContext context,IMapper mapper)
{
this.context = context;
this.mapper = mapper;
}
public async Task<GetProjectsResponse> Handle(GetProjectsRequest request, CancellationToken cancellationToken)
{
IReadOnlyList<ProjectDto> projects = await context
.Projects
.ProjectTo<ProjectDto>(mapper.ConfigurationProvider)
.ToListAsync(cancellationToken);
return new GetProjectsResponse
{
Projects = projects
};
}
}
Simplified unit test looks like the following:
public class GetProjectsTests
{
[Fact]
public async Task GetProjectsTest()
{
var projects = new List<Project>
{
new Project
{
Id = 1,
Name = "Test"
}
};
var context = new Mock<IMyDbContext>();
context.Setup(c => c.Projects).Returns(projects.AsQueryable().BuildMockDbSet().Object);
var mapper = new Mock<IMapper>();
mapper.Setup(x => x.ConfigurationProvider)
.Returns(
() => new MapperConfiguration(
cfg => { cfg.CreateMap<Project, ProjectDto>(); }));
var getProjectsRequest = new GetProjectsRequest();
var handler = new GetProjectsHandler(context.Object, mapper.Object);
GetProjectsResponse response = await handler.Handle(getProjectsRequest, CancellationToken.None);
Assert.True(response.Projects.Count == 1);
}
}
I agree that it's a debatable question how should we mock DBSet in unit tests. I think that InMemoryDatabase should be used in integration tests rather than unit tests.
Related
I have a Web API configured to send a POST to the SQL server.
I also have a server app (SignalR) that sends a list of strings to this POST API.
The problem: The Post only receives one item per time, so I send a request multiple times inside a loop, and with each iteration, a new item is sent.
It works but I believe there's an optimized way to do this, and if something goes wrong inside an iteration, the correct thing to do was canceling the transaction, but with this loop method, it is not possible.
I'm accepting tips on how to handle this better.
WebApi:
VisitaItemControl.cs
public class VisitaItemControl
{
public string ItemID { get; set; }
public string VisitaID { get; set; }
}
VisitaItemControlController.cs
[Route("api/[controller]")]
[ApiController]
public class VisitaItemControlController : ControllerBase
{
private readonly IConfiguration _configuration;
public VisitaItemControlController(IConfiguration configuration)
{
_configuration = configuration;
}
[HttpPost]
public JsonResult Post(VisitaItemControl visitaItemControl)
{
string query = #"INSERT INTO VisitaItemControl (
ItemID,
VisitaID)
VALUES (
#ItemID,
#VisitaID
)";
DataTable dt = new DataTable();
string sqlDataSource = _configuration.GetConnectionString("connectionstring");
SqlDataReader sqlDataReader;
using (SqlConnection sqlConnection = new SqlConnection(sqlDataSource))
{
sqlConnection.Open();
using (SqlCommand cmd = new SqlCommand(query, sqlConnection))
{
cmd.Parameters.AddWithValue(#"ItemID", visitaItemControl.ItemID);
cmd.Parameters.AddWithValue(#"VisitaID", visitaItemControl.VisitaID);
sqlDataReader = cmd.ExecuteReader();
dt.Load(sqlDataReader);
sqlDataReader.Close();
sqlConnection.Close();
}
}
return new JsonResult("Saved!");
}
}
SignalR app:
foreach (var item in addedItems)
{
var postObject = new VisitaItemControl()
{
ItemID = item.ItemID,
VisitaID = empObj.VisitaID,
};
var request2 = new HttpRequestMessage(HttpMethod.Post, config["API_POST"]);
request2.Content = new StringContent(JsonSerializer.Serialize(postObject), null, "application/json");
var client2 = ClientFactory.CreateClient();
var response2 = await client.SendAsync(request2);
using var responseStream2 = await response2.Content.ReadAsStreamAsync();
string res2 = await JsonSerializer.DeserializeAsync<string>(responseStream2);
}
await JS.InvokeVoidAsync("alert", "Saved!");
await refreshList();
uriHelper.NavigateTo("/", forceLoad: true);
}
Here's the basics of a structured approach to what you're trying to do.
I've used Entity Framework to manage the database and the InMemory Implemnentation for demo purposes. I've implemented everything in a Blazor Server project so we can test and manage the data in the UI and use Postman for interacting with the API.
Project Packages:
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="6.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.4" />
<PackageReference Include="System.Net.Http.Json" Version="6.0.0" />
</ItemGroup>
My InMemory Db Context:
public class InMemoryDbContext : DbContext
{
public DbSet<VisitaItemControl>? VisitaItemControl { get; set; }
public InMemoryDbContext(DbContextOptions<InMemoryDbContext> options) : base(options) { }
}
We'll use a DBContextFactory service to manage the DB connections that will use this as it's source DbContext.
My Data Broker Interface - this will normally implement all the CRUD processes. We use an interface to decouple the application from the data store.
public interface IDataBroker
{
public ValueTask<bool> AddItems<TRecord>(IEnumerable<TRecord> items) where TRecord : class;
public ValueTask<IEnumerable<TRecord>> GetItems<TRecord>(int count) where TRecord : class;
}
And my Server implementation - note I inject the DbContextFactory to manage my database connections.
public class ServerDataBroker : IDataBroker
{
private readonly IDbContextFactory<InMemoryDbContext> database;
public ServerDataBroker(IDbContextFactory<InMemoryDbContext> db)
=> this.database = db;
public async ValueTask<bool> AddItems<TRecord>(IEnumerable<TRecord> items) where TRecord : class
{
var result = false;
using var dbContext = database.CreateDbContext();
foreach (var item in items)
dbContext.Add(item);
var rowsAdded = await dbContext.SaveChangesAsync();
if (rowsAdded == items.Count())
result = true;
// Do something if not all rows are added
return result;
}
public async ValueTask<IEnumerable<TRecord>> GetItems<TRecord>(int count) where TRecord : class
{
using var dbContext = database.CreateDbContext();
return await dbContext.Set<TRecord>()
.Take(count)
.ToListAsync();
}
}
For the UI I've built a very simple View Service to hold and manage the data:
public class VisitaItemControlService
{
private IDataBroker _broker;
public event EventHandler? ListUpdated;
public IEnumerable<VisitaItemControl> Records { get; protected set; } = new List<VisitaItemControl>();
public VisitaItemControlService(IDataBroker dataBroker)
=> _broker = dataBroker;
public async ValueTask<bool> AddItems(IEnumerable<VisitaItemControl> items)
{
var result = await _broker.AddItems<VisitaItemControl>(items);
if (result)
{
await this.GetItems(1000);
this.ListUpdated?.Invoke(this, EventArgs.Empty);
}
return result;
}
public async ValueTask GetItems(int count)
=> this.Records = await _broker.GetItems<VisitaItemControl>(count);
}
And here's my Index page to test the system.
#page "/"
#inject VisitaItemControlService service;
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
<div>
<button class="btn btn-primary" #onclick=AddItems>Add Some Items</button>
</div>
#if (loaded)
{
#foreach (var item in this.service.Records)
{
<div class="p-2">
<span>
Item : #item.ItemID
</span>
<span>
Visita : #item.VisitaID
</span>
</div>
}
}
#code {
private bool loaded = false;
protected async override Task OnInitializedAsync()
{
await this.service.GetItems(1000);
this.service.ListUpdated += this.OnListUpdated;
this.loaded = true;
}
private async Task AddItems()
{
var addList = new List<VisitaItemControl> {
new VisitaItemControl { ItemID = Guid.NewGuid().ToString(), VisitaID = "AA" },
new VisitaItemControl { ItemID = Guid.NewGuid().ToString(), VisitaID = "BB" },
new VisitaItemControl { ItemID = Guid.NewGuid().ToString(), VisitaID = "CC" }
};
await this.service.AddItems(addList);
}
private void OnListUpdated(object? sender, EventArgs e)
=> this.InvokeAsync(StateHasChanged);
}
Note the use of events to notify the UI that the list has changed and trigger a re-render.
Here's my API controller:
[ApiController]
public class VisitaItemControlController : ControllerBase
{
private IDataBroker _dataBroker;
public VisitaItemControlController(IDataBroker dataBroker)
=> _dataBroker = dataBroker;
[Route("/api/[controller]/list")]
[HttpGet]
public async Task<IActionResult> GetRecordsAsync()
{
var list = await _dataBroker.GetItems<VisitaItemControl>(1000);
return Ok(list);
}
[Route("/api/[controller]/addlist")]
[HttpPost]
public async Task<bool> AddRecordsAsync([FromBody] IEnumerable<VisitaItemControl> records)
=> await _dataBroker.AddItems(records);
}
And finally Program to configure all the services and middleware.
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddDbContextFactory<InMemoryDbContext>(options => options.UseInMemoryDatabase("TestDb"));
builder.Services.AddSingleton<IDataBroker, ServerDataBroker>();
builder.Services.AddScoped<VisitaItemControlService>();
builder.Services.AddSingleton<WeatherForecastService>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.MapControllers();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.Run();
Some postman screen captures:
And the project structure:
We use Refit for our API declarations and want to use Interface inheritance.
A sample is shown here: https://github.com/reactiveui/refit#interface-inheritance
public interface IPlatformRestClient
{
HttpClient Client { get; }
[Get("/version")]
Task<string> GetVersion();
}
public interface ITestRestClient : IPlatformRestClient
{
[Get("/test")]
Task<string> Test();
}
I may be blind and do something wrong, but the construct results in the following error message
'AutoGeneratedITestRestClient' does not implement interface member 'IPlatformRestClient.GetVersion()' Platform.RestClient.UnitTests
And if I open the stubs:
/// <inheritdoc />
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
[global::System.Diagnostics.DebuggerNonUserCode]
[Preserve]
[global::System.Reflection.Obfuscation(Exclude=true)]
partial class AutoGeneratedITestRestClient : ITestRestClient
{
/// <inheritdoc />
public HttpClient Client { get; protected set; }
readonly IRequestBuilder requestBuilder;
/// <inheritdoc />
public AutoGeneratedITestRestClient(HttpClient client, IRequestBuilder requestBuilder)
{
Client = client;
this.requestBuilder = requestBuilder;
}
/// <inheritdoc />
Task<string> ITestRestClient.Test()
{
var arguments = new object[] { };
var func = requestBuilder.BuildRestResultFuncForMethod("Test", new Type[] { });
return (Task<string>)func(Client, arguments);
}
}
So is this feature broken or do I have any error?
I am using AutoMapper 6.2.0 in my ASP.NET MVC 5 application.
When I call my view through controller it shows all things right. But, when I refresh that view, Visual Studio shows an error:
System.InvalidOperationException: 'Mapper already initialized. You must call Initialize once per application domain/process.'
I am using AutoMapper only in one controller. Not made any configuration in any place yet nor used AutoMapper in any other service or controller.
My controller:
public class StudentsController : Controller
{
private DataContext db = new DataContext();
// GET: Students
public ActionResult Index([Form] QueryOptions queryOptions)
{
var students = db.Students.Include(s => s.Father);
AutoMapper.Mapper.Initialize(cfg =>
{
cfg.CreateMap<Student, StudentViewModel>();
});
return View(new ResulList<StudentViewModel> {
QueryOptions = queryOptions,
Model = AutoMapper.Mapper.Map<List<Student>,List<StudentViewModel>>(students.ToList())
});
}
// Other Methods are deleted for ease...
Error within controller:
My Model class:
public class Student
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string CNIC { get; set; }
public string FormNo { get; set; }
public string PreviousEducaton { get; set; }
public string DOB { get; set; }
public int AdmissionYear { get; set; }
public virtual Father Father { get; set; }
public virtual Sarparast Sarparast { get; set; }
public virtual Zamin Zamin { get; set; }
public virtual ICollection<MulaqatiMehram> MulaqatiMehram { get; set; }
public virtual ICollection<Result> Results { get; set; }
}
My ViewModel Class:
public class StudentViewModel
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string CNIC { get; set; }
public string FormNo { get; set; }
public string PreviousEducaton { get; set; }
public string DOB { get; set; }
public int AdmissionYear { get; set; }
public virtual FatherViewModel Father { get; set; }
public virtual SarparastViewModel Sarparast { get; set; }
public virtual ZaminViewModel Zamin { get; set; }
}
If you want/need to stick with the static implementation in a unit testing scenario, note that you can call AutoMapper.Mapper.Reset() before calling initialize. Do note that this should not be used in production code as noted in the documentation.
Source: AutoMapper documentation.
When you refresh the view you are creating a new instance of the StudentsController -- and therefore reinitializing your Mapper -- resulting in the error message "Mapper already initialized".
From the Getting Started Guide
Where do I configure AutoMapper?
If you're using the static Mapper method, configuration should only happen once per AppDomain. That means the best place to put the configuration code is in application startup, such as the Global.asax file for ASP.NET applications.
One way to set this up is to place all of your mapping configurations into a static method.
App_Start/AutoMapperConfig.cs:
public class AutoMapperConfig
{
public static void Initialize()
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Student, StudentViewModel>();
...
});
}
}
Then call this method in the Global.asax.cs
protected void Application_Start()
{
App_Start.AutoMapperConfig.Initialize();
}
Now you can (re)use it in your controller actions.
public class StudentsController : Controller
{
public ActionResult Index(int id)
{
var query = db.Students.Where(...);
var students = AutoMapper.Mapper.Map<List<StudentViewModel>>(query.ToList());
return View(students);
}
}
I've used this method before and it worked till version 6.1.1
Mapper.Initialize(cfg => cfg.CreateMap<ContactModel, ContactModel>()
.ConstructUsing(x => new ContactModel(LoggingDelegate))
.ForMember(x => x.EntityReference, opt => opt.Ignore())
);
Since version 6.2, this doesn't work any more. To correctly use Automapper create a new Mapper and us this one like this:
var mapper = new MapperConfiguration(cfg => cfg.CreateMap<ContactModel, ContactModel>()
.ConstructUsing(x => new ContactModel(LoggingDelegate))
.ForMember(x => x.EntityReference, opt => opt.Ignore())).CreateMapper();
var model = mapper.Map<ContactModel>(this);
In case you really need to "re-initialize" AutoMapper you should switch to the instance based API to avoid System.InvalidOperationException: Mapper already initialized. You must call Initialize once per application domain/process.
For example, when you are creating the TestServer for xUnit tests you can just set ServiceCollectionExtensions.UseStaticRegistration inside fixure class constructor to false to make the trick:
public TestServerFixture()
{
ServiceCollectionExtensions.UseStaticRegistration = false; // <-- HERE
var hostBuilder = new WebHostBuilder()
.UseEnvironment("Testing")
.UseStartup<Startup>();
Server = new TestServer(hostBuilder);
Client = Server.CreateClient();
}
For Unit Testing, you can add Mapper.Reset() to your unit test class
[TearDown]
public void TearDown()
{
Mapper.Reset();
}
You can use automapper as Static API and Instance API ,
Mapper already initialized is common issue in Static API , you can use mapper.Reset()
where you initialized mapper but this this not an answer at all.
Just try with instance API
var students = db.Students.Include(s => s.Father);
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<Student, StudentViewModel>();
});
IMapper iMapper = config.CreateMapper();
return iMapper.Map<List<Student>, List<StudentViewModel>>(students);
Automapper 8.0.0 version
AutoMapper.Mapper.Reset();
Mapper.Initialize(
cfg => {
cfg.CreateMap<sourceModel,targetModel>();
}
);
You can simply use Mapper.Reset().
Example:
public static TDestination MapToObject<TSource, TDestination>(TSource Obj)
{
Mapper.Initialize(cfg => cfg.CreateMap<TSource, TDestination>());
TDestination tDestination = Mapper.Map<TDestination>(Obj);
Mapper.Reset();
return tDestination;
}
If you are using MsTest you can use the AssemblyInitialize attribute so that mapping gets configured only once for that assembly (here test assembly). This is generally added into to the base class of controller unit tests.
[TestClass]
public class BaseUnitTest
{
[AssemblyInitialize]
public static void AssemblyInit(TestContext context)
{
AutoMapper.Mapper.Initialize(cfg =>
{
cfg.CreateMap<Source, Destination>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.EmailAddress));
});
}
}
I hope this answer helps
If you are using Mapper in UnitTest and your tests more then one, You may use Mapper.Reset()
`
//Your mapping.
public static void Initialize()
{
Mapper.Reset();
Mapper.Initialize(cfg =>
{
cfg.CreateMap<***>
}
//Your test classes.
[TestInitialize()]
public void Initialize()
{
AutoMapping.Initialize();
}`
private static bool _mapperIsInitialized = false;
public InventoryController()
{
if (!_mapperIsInitialized)
{
_mapperIsInitialized = true;
Mapper.Initialize(
cfg =>
{
cfg.CreateMap<Inventory, Inventory>()
.ForMember(x => x.Orders, opt => opt.Ignore());
}
);
}
}
Can someone maybe explain to me what this means and why am i getting it.
System.InvalidOperationException : When called from 'VisitLambda',
rewriting a node of type 'System.Linq.Expressions.ParameterExpression'
must return a non-null value of the same type. Alternatively, override
'VisitLambda' and change it to not visit children of this type.
I am getting it from my unit tests I am running the latest .net core 2 with EF core. all my tests were fine till i upgraded then i started getting the error.
The funny thing is, is that when i run the project the line were it fails in the the tests is ok.
This is my Test
[Fact]
public async Task GetUserProfileAsync_Where_Employee_Exist_Test()
{
// Given
var user = TestPrincipal.CreatePrincipalForEmployeeUser();
using (var factory = new TestContextFactory())
using (var context = factory.CreateInMemoryDatabase<ApplicationContext>())
{
this.SetDependencies(context);
var data = EmployeeValueHelper.GetEmployeeValues();
context.AddRange(data);
context.SaveChanges();
var sut = new ProfileService(new DbContextRepository<Data.Models.Employees.Employee>(context), this.userService, this.moqEmploymentStatusService.Object);
// When
// -> this method goes to a service and calls the below FindByIdAsync
var actual = await sut.GetProfileForUserAsync(user);
// Then
Assert.Equal(10, actual.EmployeeId);
}
}
public async Task<Employee> FindByIdAsync(long id)
{
var profile = await this.repository.Set
.Include(_ => _.Address) --> IT FAILS ON THIS LINE, IF I REMOVE THE INCLUDE THEN IT WORKS
.Include(_ => _.EmployeeImage)
.SingleOrDefaultAsync(_ => _.EmployeeId == id);
if (profile == null)
{
return null;
}
return profile;
}
UPDATE
Service Layer
public class ProfileService : GenericService<Employee>, IProfileService
{
private readonly DbContextRepository<Employee> repository;
private readonly IUserService userService;
public ProfileService(DbContextRepository<Employee> repository, IUserService userService)
: base(repository)
{
this.repository = repository;
this.userService = userService;
}
public Task<Employee> GetProfileForUserAsync(ClaimsPrincipal user)
{
var id = this.userService.GetEmployeeId(user);
return id.HasValue ? this.FindByIdAsync(id.Value) : null;
}
public async Task<Employee> FindByIdAsync(long id)
{
var profile = await this.repository.Set
.Include(_ => _.Address)
.Include(_ => _.EmployeeImage)
.SingleOrDefaultAsync(_ => _.EmployeeId == id);
if (profile == null)
{
return null;
}
return profile;
}
}
Employee Model
public class Employee : IValidatableObject
{
[Key]
[Column("pkEmpID")]
public long EmployeeId { get; set; }
[Column("fkCompanyID")]
public long CompanyId { get; set; }
public virtual Company Company { get; set; }
[Display(Name = "lblEmpNumber")]
public string EmpNumber { get; set; }
public virtual IList<Address> Address { get; set; } = new List<Address>();
// WITH SOME EXTRA STUFF NOT NEEDED FOR THIS
}
Repository
public class DbContextRepository<TEntity> : IGenericRepository<TEntity>, IDisposable
where TEntity : class
{
public DbContextRepository(ApplicationContext context)
{
this.Context = context;
this.Set = context.Set<TEntity>();
this.SetWithNoTracking = this.Set.AsNoTracking();
}
public ApplicationContext Context { get; }
public DbSet<TEntity> Set { get; }
public IQueryable<TEntity> SetWithNoTracking { get; }
// WITH SOME EXTRA STUFF NOT NEEDED FOR THIS
}
Hope this will shed more light
<ItemsControl DockPanel.Dock="Right" x:Name="Actions">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button x:Name="Action"
HorizontalAlignment="Right"
Content="{Binding Label}"
Margin="3" Width="30"></Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
The above view binds with this viewmodel
public class DeploymentInputViewModel<T> : PropertyChangedBase
{
public BindableCollection<InputActionViewModel> Actions {get;set;}
}
I see my buttons. But when clicking it nothing happen.
The viewModels for InputActionViewModel:
public abstract class InputActionViewModel{
public InputActionViewModel()
{
}
public virtual Task Action()
{
return Task.FromResult<object>(null);
}
public string ActionToolTip { get; set; }
public string Label { get; set; }
public object Value { get; set; }
}
and also
public class InputCertificateActionViewModel : InputActionViewModel
{
[Import]
private IShell _shell;
[Import]
private IWindowsDialogs _dialogs;
private readonly IDeploymentSettingInputViewModel vm;
public InputCertificateActionViewModel(IDeploymentSettingInputViewModel vm)
{
this.vm = vm;
Label = "...";
ActionToolTip = "Pick a Certificate";
}
public bool IsManagementCertificate {get;set;}
public bool IsDeploymentCertificate { get; set; }
public async override Task Action()
{
if(IsManagementCertificate)
{
var subs = await _shell.IdentityModel.GetEnabledSubscriptionsAsync();
foreach(var sub in subs)
{
using (ManagementClient client = CloudContext.Clients.CreateManagementClient(sub.GetCredentials()))
{
var cert = _dialogs.SelectItemDialog("Select a certificate", "Pick one", true,
(await client.ManagementCertificates.ListAsync()).Select(c =>
new SelectItem(c.Thumbprint, Encoding.Default.GetString(c.PublicKey), c, (s) => c.Thumbprint.Contains(s))).ToArray())
.Tag as ManagementCertificateListResponse.SubscriptionCertificate;
this.vm.Value = cert.Thumbprint;
}
}
}else if(IsDeploymentCertificate)
{
}
}
}
I am adding actionViewModels by inserting directly into the observable code at startup.
haveActions.Actions.Add(DI.BuildUp(new InputCertificateActionViewModel(vm)
{
IsDeploymentCertificate = certAttribute.IsDeploymentCertificate,
IsManagementCertificate = certAttribute.IsManagementCertificate,
}));
haveActions is an instance of InputCertificateActionViewModel
Couldn't fit this all in a comment:
I can't have a peek at the Caliburn.Micro at the moment, but it might be something related to calling your method Action.
At a guess though, I'd say that by convention Caliburn.Micro expects to find a method that matches the Action<T> delegate to use for it's Actions, so your public virtual Task Action() won't be located and bound.
Have a quick check by defining a new method with a compatible signature, e.g public void MyMethod() and checking to see if it's located correctly and will function.
If that is the problem, you'll probably want to have a look at the IResult and Coroutines part of the Caliburn.Micro documentation, which looks like it will help you implement your desired behaviour.