We are working on creating a suite of automated playwright tests for our app through specflow. I have been trying to come up with a way to use specflow hooks to seed data before/clean up data after each test so that we can ensure that no tests are interfering with each other. Elsewhere in our program, functions are able to receive the DbContextFactory through dependency injection:
public async Task<ClientPayload> AddClientAsync(AddClientInput input, [Service] IDbContextFactory<ProjectDbContext> dbContextFactory, [Service] IMapper mapper, CancellationToken cancellationToken)
{
return new(await this.AddEntityAsync<AddClientInput, Client>(input, dbContextFactory, mapper, cancellationToken));
}
But trying to do this in a specflow hooks file like this:
public class ActivitiesHooks
{
private readonly IDbContextFactory<ProjectDbContext> myDbContextFactory;
public ActivitiesHooks([Service] IDbContextFactory<ProjectDbContext> _myDbContextFactory)
{
this.myDbContextFactory = _myDbContextFactory;
}
[BeforeScenario, Scope(Scenario = "Editing an Activity")]
public void BeforeEditingAnActivity()
{
using var dbContext = ProjectDbContextFactory.CreateDbContext();
var activityToUpdate = new Activity { ... };
dbContext.Set<Activity>().Add(activityToUpdate);
dbContext.SaveChanges();
}
generates a strange error when I try to run the actual test (It builds just fine):
Error Message:
BoDi.ObjectContainerException : Interface cannot be resolved: Microsoft.EntityFrameworkCore.IDbContextFactory`1[[Project.Framework.EntityFramework.ProjectDbContext, Project.Framework, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] (resolution path: Project.Specs.Hooks.ActivitiesHooks)
TearDown : BoDi.ObjectContainerException : Interface cannot be resolved: Microsoft.EntityFrameworkCore.IDbContextFactory`1[[Project.Framework.EntityFramework.ProjectDbContext, Project.Framework, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] (resolution path: Project.Specs.Hooks.ActivitiesHooks)
The hooks file is not using BoDi, although it is in use elsewhere in the code, so I'm not sure why that's included in the error. I've also tried inject just the DbContext, not the DbContextFactory, and that generates an identical error. Is there a fix for this, or is there a better way to use DbContext to seed data for tests?
Related
I've been developing a .NET Core 6 console application (not ASP.NET) the last weeks and now I've tried to implement Entity Framework 6 migrations to it.
However, even though I reused some code from a working database model that used migrations, now I can't manage to make it work and I've also been struggling due to the lack of output from dotnet-ef.
For reasons I can't remember, the database project I reused code from used Design-Time DbContext creation. I don't know if that's my optimal way to make migrations but at least it managed to work on the previous project. I implemented the required IDesignTimeDbContextFactory<DbContext> interface the same way it was done previously:
public class MySqlContextFactory : IDesignTimeDbContextFactory<MySqlContext>
{
public MySqlContext CreateDbContext(string[] args)
{
DbContextOptionsBuilder optionsBuilder = new();
ServerVersion mariaDbVersion = new MariaDbServerVersion(new Version(10, 6, 5));
optionsBuilder.UseMySql(DatabaseCredentials.GetConnectionString(), mariaDbVersion);
return new MySqlContext();
}
}
public class MySqlContext : DbContext
{
public DbSet<Endpoint> EndpointsSet { get; set; }
private readonly string _connectionString;
public MySqlContext() : base()
=> _connectionString = DatabaseCredentials.GetConnectionString();
public MySqlContext(string connectionString) : base()
=> _connectionString = connectionString;
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> Configurator.Configure(optionsBuilder, _connectionString);
protected override void OnModelCreating(ModelBuilder modelBuilder)
=> Configurator.Create(modelBuilder);
}
public static void Configure(DbContextOptionsBuilder optionsBuilder, string connectionString)
{
ServerVersion mariaDbVersion = new MariaDbServerVersion(new Version(10, 6, 5));
optionsBuilder.UseMySql(connectionString, mariaDbVersion);
}
public static void Create(ModelBuilder modelBuilder)
{
IEnumerable<Type> types = ReflectionUtils.GetImplementedTypes(typeof(IEntityTypeConfiguration<>));
if (types.Any())
{
foreach (Type entityConfigurationType in types)
{
modelBuilder.ApplyConfigurationsFromAssembly(entityConfigurationType.Assembly);
}
}
else
{
Environment.Exit((int) EExitCodes.EF_MODEL_NOT_FOUND);
}
}
However, when I tried to create the first migration, I've been prompted with this absolutely non-descriptive output from the dotnet-ef tool:
PS> dotnet ef migrations add Init
Build started...
Build succeeded.
PS>
But no migrations were made nor anything changed in my project. So I decide to force dotnet ef to tell me more things by appending the --verbose flag on the PS command:
[...]
Build succeeded.
dotnet exec --depsfile F:\pablo\Documents\source\MyBot\bin\Debug\net6.0\MyBot.deps.json --additionalprobingpath C:\Users\pablo\.nuget\packages --runtimeconfig F:\pablo\Documents\source\MyBot\bin\Debug\net6.0\MyBot.runtimeconfig.json C:\Users\pablo\.dotnet\tools\.store\dotnet-ef\6.0.1\dotnet-ef\6.0.1\tools\netcoreapp3.1\any\tools\netcoreapp2.0\any\ef.dll migrations add Init -o Migrations\Init --assembly F:\pablo\Documents\source\MyBot\bin\Debug\net6.0\MyBot.dll --project F:\pablo\Documents\source\MyBot\MyBot.csproj --startup-assembly F:\pablo\Documents\source\MyBot\bin\Debug\net6.0\MyBot.dll --startup-project F:\pablo\Documents\source\MyBot\MyBot.csproj --project-dir F:\pablo\Documents\source\MyBot\ --root-namespace MyBot--language C# --framework net6.0 --nullable --working-dir F:\pablo\Documents\source\MyBot--verbose
Using assembly 'MyBot'.
Using startup assembly 'MyBot'.
Using application base 'F:\pablo\Documents\source\MyBot\bin\Debug\net6.0'.
Using working directory 'F:\pablo\Documents\source\MyBot'.
Using root namespace 'MyBot'.
Using project directory 'F:\pablo\Documents\source\MyBot\'.
Remaining arguments: .
Finding DbContext classes...
Finding IDesignTimeDbContextFactory implementations...
Found IDesignTimeDbContextFactory implementation 'MySqlContextFactory'.
Found DbContext 'MySqlContext'.
Finding application service provider in assembly 'MyBot'...
Finding Microsoft.Extensions.Hosting service provider...
No static method 'CreateHostBuilder(string[])' was found on class 'Program'.
No application service provider was found.
Finding DbContext classes in the project...
Using DbContext factory 'MySqlContextFactory'.
PS>
The first thing I thought I could search for was that CreateHostBuilder function the tool is searching but not retrieving. However, once again, all the documentation I could find was refer to ASP.NET applications, and programming patterns I'm not implementing in my bot application. My app does retrieve the services via Dependency Injection, custom made (maybe that's the reason of the line No application service provider was found. ?), but I didn't find a way to implement that CreateHostBuilder function without changing everything.
Just for adding the information, this is how I managed to create and configure the EF model with the non-migrations approach:
public static IServiceProvider GetServices(DiscordSocketClient client, CommandService commands)
{
ServiceCollection services = new();
services.AddSingleton(client);
services.AddSingleton(commands);
services.AddSingleton<HttpClient>();
services.AddDbContext<MySqlContext>(ServiceLifetime.Scoped);
return AddServices(services) // builds service provider;
}
private static async Task InitDatabaseModel(IServiceProvider provider)
{
MySqlContext? dbCtxt = provider.GetService<MySqlContext>();
if (dbCtxt == null)
{
Environment.Exit((int) EExitCodes.DB_SERVICE_UNAVAILABLE);
}
await dbContext.Database.EnsureDeletedAsync();
await dbContext.Database.EnsureCreatedAsync();
}
But unfortunately, my application is planned to interact with a database dynamically, so the Code-First configuring approach is not valid for me.
How can I solve this? Is an approach problem, or am I messing around with the custom non ASP.NET Dependency Injection provider? Thank you all
There is an issue with your IDesignTimeDbContextFactory. EF Core is trying to your this factory to create a MySqlContext.
public class MySqlContextFactory : IDesignTimeDbContextFactory<MySqlContext>
{
public MySqlContext CreateDbContext(string[] args)
{
// set up options
DbContextOptionsBuilder optionsBuilder = new();
ServerVersion mariaDbVersion = new MariaDbServerVersion(new Version(10, 6, 5));
optionsBuilder.UseMySql(DatabaseCredentials.GetConnectionString(), mariaDbVersion);
// *** this is the issue ***
// return default constructor W/O options (ie, UseMySql is never called)
return new MySqlContext();
}
}
You can add this constructor to your DbContext class:
public MySqlContext(DbContextOptions<MySqlContext> options)
: base(options)
{
}
and then return new MySqlContext(optionsBuilder.Options) from your factory.
I'm trying to use resharper xunit testing.
I setup a test class like this
public class CropLossLandingPageServiceTest
{
private readonly IWPCropLossRequestService cropLossRequestService;
public CropLossLandingPageServiceTest(IWPCropLossRequestService cropLossRequestService)
{
this.cropLossRequestService = cropLossRequestService;
}
[Fact(DisplayName = "Banner Message Test")]
public async void GetBannerMessageTest()
{
var result = this.cropLossRequestService.GetBannerMessage();
Assert.NotNull(result);
}
}
Get this error
System.TypeLoadException Could not load type
'Microsoft.EntityFrameworkCore.Infrastructure.IDbContextOptionsExtensionWithDebugInfo'
from assembly 'Microsoft.EntityFrameworkCore, Version=5.0.8.0,
Culture=neutral, PublicKeyToken=adb9793829ddae60'. at
xxx.WUOM.Data.Models.WFOrderMgmtContext.OnConfiguring(DbContextOptionsBuilder
optionsBuilder) at
Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider()
Do I have to setup dependency injection in the test class? I should not have to change GetBannerMessageTest().
related link: Unable to resolve service for type 'Microsoft.AspNetCore.Hosting.IHostingEnvironment
I want to test a Xamarin view model with xUnit. When the code is build using command line on Mac, the following error are show:
/usr/local/share/dotnet/sdk/3.1.300/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(283,5): error NETSDK1073: The FrameworkReference 'Microsoft.WindowsDesktop.App.WPF' was not recognized
If <GenerateErrorForMissingTargetingPacks>false</GenerateErrorForMissingTargetingPacks> is used on .csproj, the project compiles, but the following error are reported when I try to run the test.
System.BadImageFormatException : Duplicate type with name 'App.<>PropertyChangedEventArgs'
The view model are show below (part of the class). Fody and PropertyChanged.Fody are used to automate the implementation of INotifyPropertyChanged.
[AddINotifyPropertyChangedInterface]
public class ListaTarefasViewModel : ViewModelBase, IHandleViewAppearing, IHandleViewDisappearing
{
public ListaTarefasViewModel(
ITarefaService tarefaService,
ITarefaRepository tarefaRepository,
ITarefaRetornoItensRepository tarefaRetornoItensRepository,
INotificationService notificationService,
IUsuarioRepository usuarioRepository,
IProdutoRepository produtoRepository)
{
this.tarefaService = tarefaService;
this.tarefaRepository = tarefaRepository;
this.notificationService = notificationService;
this.usuarioRepository = usuarioRepository;
this.tarefaRetornoItensRepository = tarefaRetornoItensRepository;
this.produtoRepository = produtoRepository;
}
// ...
}
The test class:
public class ListaTarefasViewModelTest : IDisposable
{
private readonly Mock<ListaTarefasViewModel> listaTarefasViewModelMock;
public ListaTarefasViewModelTest()
{
listaTarefasViewModelMock = new Mock<ListaTarefasViewModel>();
}
public void Dispose()
{
}
[Fact]
public async Task ShouldConfigureTipoTarefaWhenInitializeAsync()
{
object tipoTarefa = TipoTarefaEnum.Inventario;
await listaTarefasViewModelMock.Object.InitializeAsync(tipoTarefa);
Assert.Equal(TipoTarefaEnum.Inventario, listaTarefasViewModelMock.Object.TipoTarefa);
}
}
Build and Execution Errors
The error
/usr/local/share/dotnet/sdk/3.1.300/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(283,5): error NETSDK1073: The FrameworkReference 'Microsoft.WindowsDesktop.App.WPF' was not recognized
was caused by the use of the package Rg.Plugins.Popup, because it depends of WPF through Xamarin.Forms (Xamarin.Forms.Platform.WPF). This can be resolved by using <PrivateAssets>all</PrivateAssets> on the .csproj file.
Example:
<PackageReference Include="Rg.Plugins.Popup" Version="2.0.0.3">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
Reference about the .csproj file configuration: Package references (PackageReference) in project files
The error
System.BadImageFormatException : Duplicate type with name 'App.<>PropertyChangedEventArgs'
was solved by cleaning the entire solution or the shared project, but this is needed to be made before any test. This appears to be caused by Fody or PropertyChanged.Fody.
That are issues related to the this error, but none was resolved by now: issue on PropertyChanged.Fody repository and issue on MarcStan /
resource-embedder repository.
Unit Test
Finally, the code uses Autofac and the test was made with xUnit.
The class was tested with the mock getting all the dependencies from another mocks.
var tarefaService = Mock.Of<ITarefaService>();
var tarefaRepository = Mock.Of<ITarefaRepository>();
// ...
var mockListaTarefasViewModel = new Mock<ListaTarefasViewModel>(
MockBehavior.Loose,
tarefaService,
tarefaRepository,
// ..
);
mockListaTarefasViewModel
.Setup(/* .. */)
.Verifiable();
mockListaTarefasViewModel.Verify();
I have three different applications that all build with the same business layer/data layer. I'm adding a IUserNameProvider to the IUnitOfWork class that is used by all three applications. Because the each application get the User names using a different method, I created the IUserNameProvider and I'm injecting the appropriate implementation using Autofac.
This seems like it should be fairly straightforward code, but I can't get it configured correctly for the Web API application. Similar code is working fine in the console application
My Global.asax.cs
protected void Application_Start()
{
var builder = new ContainerBuilder();
builder.RegisterType<WebAPIGetUser>().As<SSEMPA.DataAccess.Infrastructure.IUserNameProvider>();
builder.RegisterType<UnitOfWork>().As<IUnitOfWork>()
.WithParameter("connectionString", ConfigurationManager.ConnectionStrings["MyDataContex"].ConnectionString);
//other type registrations ...
DependencyResolver.SetResolver(new AutofacDependencyResolver(builder.Build()));
// other registrations ....
}
My UnitOfWork constructor
public class UnitOfWork : IUnitOfWork
{
private MyDataContext dataContext;
public UnitOfWork(IUserNameProvider userNameProvider, string connectionString)
{
dataContext = new MyDataContext (connectionString, userNameProvider);
}
...
}
My ApiController
public class AgencyApiController : ApiController
{
private readonly IUnitOfWork _unitOfWork;
public AgencyApiController(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
...
}
When the API gets hit, it throws the following error:
<Error>
<Message>An error has occurred.</Message>
<ExceptionMessage>An error occurred when trying to create a controller of type 'AgencyApiController'. Make sure that the controller has a parameterless public constructor.
</ExceptionMessage>
<ExceptionType>System.InvalidOperationException</ExceptionType>
<StackTrace>at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) at System.Web.Http.Controllers.HttpControllerDescriptor.CreateController(HttpRequestMessage request) at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext()
</StackTrace>
<InnerException>
<Message>An error has occurred.</Message>
<ExceptionMessage>None of the constructors found with 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type 'SSEMPA.DataAccess.Infrastructure.UnitOfWork' can be invoked with the available services and parameters: Cannot resolve parameter 'SSEMPA.DataAccess.Infrastructure.IUserNameProvider userNameProvider' of constructor 'Void .ctor(SSEMPA.DataAccess.Infrastructure.IUserNameProvider , System.String)'.
</ExceptionMessage>
<ExceptionType>Autofac.Core.DependencyResolutionException</ExceptionType>
I've tried using named parameters for both the IUserNameProvider and connections string, but that didn't change anything. It seems like this should work, so I must be missing something small.
Thanks for looking at this.
There is additional configuration and setup you need to do for WebAPI, beyond what you do for a console application. Take a look at the docs here for instructions: Autofac Web Api Integration
So this will be an interesting post because I must include all my code and will attempt to explain clearly how I have setup my architecture.
I have placed all my Service and DataContracts in a central assembly (DMT.WCF.Contracts). This is done so that the distributed pieces of my application can all reference the same type of service interfaces and contracts which is very nice.
I have setup a StructureMap container to inject my dependencies in the following manner, by specifying a ServiceContext, which will house all of the Service Interface properties so that they can be referenced int he application later.
public interface IServiceContext
{
}
public class ServiceContext: IServiceContext
{
public IAuthenticationService AuthenticationService { get; set; }
public ServiceContext(IAuthenticationService authenticationService)
{
AuthenticationService = authenticationService;
}
}
Then, I have my StructureMapControllerFactory which looks like the following:
public class StructureMapControllerFactory:DefaultControllerFactory
{
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
if (controllerType == null) return null;
return ObjectFactory.GetInstance(controllerType) as IController;
}
}
and this is configured in my global.asax like the following:
protected void Application_Start()
{
ControllerBuilder.Current.SetControllerFactory(new StructureMapControllerFactory());
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
Configure();
}
I wanted to decouple my services as much as possible from my appliction, so I have implemented the following ServiceFactory class that handles providing proxies to StructureMap when the IoC container is configured:
public static class ServiceFactory
{
private static readonly ClientSection _clientSection = ConfigurationManager.GetSection("system.serviceModel/client") as ClientSection;
public static T Create<T>()
{
T context = default(T);
foreach(ChannelEndpointElement endpoint in _clientSection.Endpoints)
{
if(endpoint.Contract == typeof(T).FullName)
{
IEnumerable<Type> assignables = typeof (Binding).Assembly.GetTypes().Where(p => typeof(Binding).IsAssignableFrom(p));
Type bindingType = assignables.Single(p => p.Name.ToLower().Equals(endpoint.Binding.ToLower()));
context = ChannelFactory<T>.CreateChannel((Binding)Activator.CreateInstance(bindingType, false), new EndpointAddress(endpoint.Address));
}
}
return context;
}
}
This allows me to pull directly from the config file when creating proxies so I do not need to select "Add Service Reference" (as that is technically adding a dependency).
In my global.asax, I can now configure my StructureMap Container like this:
protected void Configure()
{
ObjectFactory.Configure(x =>
{
x.Scan(scanner => scanner.AddAllTypesOf<IController>());
x.For<IAuthenticationService>().Use(ServiceFactory.Create<IAuthenticationService>());
x.For<IServiceContext>().Use<ServiceContext>();
});
}
Although I was initially able to use this in the following manner:
IAuthenticationService service = ServiceContext.AuthenticationService.Authenticat(...);
I am now unable to start my application without exceptions being thrown such as the following:
StructureMap configuration failures:
Error: 104
Source: Registry: StructureMap.Configuration.DSL.Registry, StructureMap, Version=2.6.1.0, Culture=neutral, PublicKeyToken=e60ad81abae3c223
Type Instance '685e2e2a-f271-4163-a6fa-ba074e4082d1' (Object: DMT.WCF.Contracts.Authentication.IAuthenticationService) cannot be plugged into type DMT.WCF.Contracts.Authentication.IAuthenticationService, DMT.WCF.Contracts, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
I am not sure why this is occuring. Like I said, I was initially able to get this up and running, but am not sure what has changed.
I have looked at the many of hundreds of references regarding this error message, but they are all specific to problems that dont seem to match mine, unless I am overlooking my problem.
HELP!!!
Doesn't this operation use the ChannelFactory to new up a channel safe client?
context = ChannelFactory<T>.CreateChannel(
(Binding)Activator.CreateInstance(bindingType, false),
new EndpointAddress(endpoint.Address));
Well, two issues here. As Sixto Saez mentioned, there's WCF issues to consider. On the StructureMap front, my guess is that your factory method may be returning a default instance for an interface.
Two suggestions...
Right after your container configuration, add a call to ObjectFactory.AssertConfigurationIsValid()...make sure you remove it again after you figure out what's wrong :-) it should throw a very similar error, but it will actually try to resolve every instance of every configured type. Usually you'll get a very verbose error with everything that's wrong. You can then start finding where your configuration error is.
It may have something to do with your factory method for the pluggable type. You might try having those instances created on the fly. Use the IContext syntax to do that - context => // Make Foo Here.
protected void Configure()
{
ObjectFactory.Configure(x =>
{
x.Scan(scanner => scanner.AddAllTypesOf<IController>());
// Skip using this
// x.For<IAuthenticationService>()
// .Use(ServiceFactory.Create<IAuthenticationService>());
// Use the IContext syntax instead. Normally you'd grab the instance out of the
// container, but you can use this to resolve an instance "live" from
// somewhere other than the container
x.For<IAuthenticationService>()
.Use(context => ServiceFactory.Create<IAuthenticationService>());
x.For<IServiceContext>().Use<ServiceContext>();
});
// Remove this from production code because it resolves the entire container...
ObjectFactory.AssertConfigurationIsValid();
}
I'm guessing that using the IContext syntax may help fix the configuration errors. You can use the Assert to go from there if not. I think the other comments cover the WCF issues, but it's kind of hard to assess those while StructureMap is misconfigured.