GithubHelp home page GithubHelp logo

Comments (10)

EjaYF avatar EjaYF commented on May 28, 2024 1

I think this is already possible using Factory registration, at least, in my implementation I have a similar issue working.

The idea is to register the implementation itself directly, and when registering the interface of the implementation to use a factory which first retrieves the ScenarioContext. Based on something in the ScenarioContext you then can return different implementations for this interface.

Pseudo-code based on the example of @304NotModified:

[ScenarioDependencies]
public static IServiceCollection CreateServices()
{
    var services = new ServiceCollection();

    services.AddScoped<IMyService, MyService>();

    // register the implementation without it's interface
    services.AddScoped<MyRepository>();

    services.AddScoped<IMyRepository>(ctx => {
        var scenarioContext = ctx.GetRequiredService<ScenarioContext>();
        if (context.Scenario == "action")
        {
            var myRepositoryMock = new Mock<IMyRepository>(); //Using Moq
            return myRepositoryMock.Object;
        }
        else
        {
            return ctx.GetRequiredService<MyRepository>();
        }
    });

    return services;
}

from specflow.dependencyinjection.

mbhoek avatar mbhoek commented on May 28, 2024

Sorry for not getting back at you earlier, crazy times.

The 'registrations' are setup before the steps are executed (i.e. BuildServiceProvider in the DependencyInjectionPlugin), so I don't think you can change those afterwards.

It is possible to change the behavior in code using the [BeforeFeature]/[BeforeScenario] hooks, as described in 'Advanced Options' on https://specflow.org/documentation/Context-Injection/ (note: those are executed for all features/scenarios, not just a specific one).

Counter-question: what is it that you would like to achieve? Could you come up with an example that helps me better understand?

from specflow.dependencyinjection.

304NotModified avatar 304NotModified commented on May 28, 2024

Indeed craze times! No problem, better late than never ;)

We sometimes need other mocks, for example, some scenarios are integration tests (not mocked), and others are mocked (for performance and stability).

Now we need to trick that or use 2 projects.

It is possible to change the behavior in code using the [BeforeFeature]/[BeforeScenario] hooks, as described in 'Advanced Options' on https://specflow.org/documentation/Context-Injection/ (note: those are executed for all features/scenarios, not just a specific one).

I guess that's fine, if I know the scenario. Maybe a new attribute for that then? e.g. [FeatureDependencies] and [AllScenarioDependencies]? (the latter is the current global)

from specflow.dependencyinjection.

mbhoek avatar mbhoek commented on May 28, 2024

I don't want to introduce functionality which allows Features or Steps to become aware that they are running a mocked or non-mocked version of the test; I think that goes against the nature of dependency injection. Also, I can imagine the same problem exist for people using other plugins (e.g. Autofac or Ninject) so that leads me to believe that this should be solved in Specflow rather than just in this particular plugin.

Information about the Scenario can be found using ScenarioContext (which is also injected). Read more about it at https://specflow.org/documentation/ScenarioContext/ (specifically ScenarioInfo).

from specflow.dependencyinjection.

304NotModified avatar 304NotModified commented on May 28, 2024

Well the difference between Autofac/ninject and ServiceCollection is that in the first libraries you could add/replace dependencies and easy create child containers. That's not the case with ServiceCollection

from specflow.dependencyinjection.

mbhoek avatar mbhoek commented on May 28, 2024

I think Replace(IServiceCollection, ServiceDescriptor) in ServiceCollectionDescriptorExtensions provides that functionality.

Although it's probably easier to register different services based upon what you want to test (mocked vs integration) in the CreateServices() method. Or have separate projects, like you already suggested. It's a bit hard to tell without knowing your specific implementation 🤔

I'll give it some more thought because I do find it an interesting use case; I'm just not sure if there's anything I can add to this plugin which can help. I'll leave the issue open in the mean time, maybe we can get some feedback from other users as well.

from specflow.dependencyinjection.

304NotModified avatar 304NotModified commented on May 28, 2024

I think Replace(IServiceCollection, ServiceDescriptor) in ServiceCollectionDescriptorExtensions provides that functionality.

Yes, but AFAIK you can't inject an IServiceCollection , only ServiceProvider which is readonly.

See also https://stackoverflow.com/questions/53580470/how-do-you-build-a-servicecollection-out-of-a-serviceprovider

It's a bit hard to tell without knowing your specific implementation

I will try to explain better :)

  • flow.feature - contains the total flow, and doesn't have mocks. Those are integration tests and use the file system and database
  • actions.feature - contains sub actions and is mocked as integration is already tested. No real file system and no real database.

For now, we have to write custom factories and builders as we have only 1 servicecollection/serviceprovider for a project. This is only needed in the feature file project. I would be nice if we could skip that.

For example, I have these interface and classes:

interface IMyRepository
{
    ...
}

class MyRepository : IMyRepository
{
    ...
}

interface IMyService
{
    ...
}

class MyService : IMyService
{
    public MyService(IMyRepository myRepository) { }

    ...
}
  • in the steps of flow.feature , I like to inject IMyService with MyService and MyRepository , so

    public static IServiceCollection CreateServices()
    {
        var services = new ServiceCollection();
    
        services.AddScoped<IMyService, MyService>();
        services.AddScoped<IMyRepository, MyRepository>();
    
        return services;
    }   
  • in the steps of actions.feature , I like to inject IMyService with MyService and mocked IMyRepository, like this, but now MyRepository registration should be removed!

    public static IServiceCollection CreateServices()
    {
        var services = new ServiceCollection();
    
        services.AddScoped<IMyService, MyService>();
        var myRepositoryMock = new Mock<IMyRepository>(); //Using Moq
        services.AddSingleton<IMyRepository>(myRepositoryMock.Object);
        
        //this should be disabled:     services.AddScoped<IMyRepository, MyRepository>();
    
        return services;
    }

So i'm looking for something like this:

[ScenarioDependencies]
public static IServiceCollection CreateServices(ScenarioContext context)
{
    var services = new ServiceCollection();

    services.AddScoped<IMyService, MyService>();
    if (context.Scenario == "action")
    {
        var myRepositoryMock = new Mock<IMyRepository>(); //Using Moq
        services.AddSingleton<IMyRepository>(myRepositoryMock.Object);
    }
    else
    {
        services.AddScoped<IMyRepository, MyRepository>();
    }

    return services;
}

from specflow.dependencyinjection.

mbhoek avatar mbhoek commented on May 28, 2024

Thanks for the eloborate example. It helps because I was thinking you wanted to use the same (1) Feature/Scenario to test both mocked and non-mocked versions. Do I understand correctly that you are okay with having separate features/scenarios (so one feature for mocked and one feature for non-mocked?).

from specflow.dependencyinjection.

304NotModified avatar 304NotModified commented on May 28, 2024

Yes that's correct :)

from specflow.dependencyinjection.

304NotModified avatar 304NotModified commented on May 28, 2024

Ow cool!

Well I think we could close this one then.

I don't have an project to test this now. I'm not working anymore where we needed this.

Anyway thx, maybe I will try this in the future!

from specflow.dependencyinjection.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.