-1

I am dealing with a issue where I am trying to instantiate a shared resource in a Selenium SpecFlow project. The resource is a pool of logins (Dictionary<Login, DateTime>).

Constraint 1: The application is not designed to be logged into concurrently with more than on login at a time. That said, many different users can log in at the same time, but the same users should not log in concurrently at the same time. So the items of the dictionary need to be limited to one thread at a time.

Constraint 2: There is no dependency injection on the project, and I am looking for the equivalent of an application state event.

Note 1: I am coming from a MVC ASP.NET background, and would do something like the global.asax App_Start() method.

Note 2: I will be accessing my Dictionary from the Step Definitions to 'reserve' logins, as well as from the hooks to 'release' the logins.

Is there anything like this in Specflow?

Thanks Devin

UPDATE 9/24/2020:

I've worked on this problem some more, and am deciding to go with a Singleton class. The Singleton is not instantiated until it is first called, then will only instantiate a single instance.

UPDATE 9/24/2020 #1:

I found a caveat to using the Singleton pattern, the issues is that our Specflow project is configured for 'Parallel Execution with Memory Isolation', meaning you cannot share state between threads, and this seems like the recommend way according to Specflow. I found this out the hard way, when I found I was getting 25 unique instances of my Dictionary<Login, DateTime>.

That being said, I am not sure if even I did implement a DI framework, that it wouldn't have the same issue.

I am looking to other forms of persistent storage now, likely going to try Azure Storage Tables.

Devin Gleason Lambert
  • 1,516
  • 1
  • 13
  • 22
  • 1
    You say you don't have it but is there Any reason you can't add dependency/context injection? I'm happy to provide you with boiler plate to help you if you need it? It might also be needed to support other areas of the framework. Specflow talk about bodi and their approach in their docs https://docs.specflow.org/projects/specflow/en/latest/Bindings/ContextInjection.html – RichEdwards Sep 22 '20 at 18:08
  • 1
    @RichEdwards, there is missed hyphen in the url, looks like this is correct one: https://docs.specflow.org/projects/specflow/en/latest/Bindings/Context-Injection.html – Renat Sep 22 '20 at 18:14
  • 1
    @Renat - that is the one! Copy and paste error. Good catch and thanks for the correction buddy! – RichEdwards Sep 22 '20 at 18:46
  • Which version of SpecFlow are you using? SpecFlow absolutely supports dependency injection. See https://docs.specflow.org/projects/specflow/en/latest/Bindings/Context-Injection.html – Greg Burghardt Sep 22 '20 at 19:36
  • dependency injection is out of scope for what I am trying to accomplish right now, but am looking to implement it soonish. Thanks for the references. Aside from standing up DI, is there any other way I can call the code once, at application start, before tests start executing. – Devin Gleason Lambert Sep 22 '20 at 20:30
  • We are using Specflow 3.0.225 – Devin Gleason Lambert Sep 22 '20 at 20:39
  • Thinking this through more, DI only partial solves my problem. I still am not clear where and how I can call my code before test start executing. – Devin Gleason Lambert Sep 22 '20 at 21:20
  • Hi Devin, use speclfow hooks: https://docs.specflow.org/projects/specflow/en/latest/Bindings/Hooks.html - This essentially means adding `[Binding]` to a class. Then, if you want your code to run once per test you've got `[BeforeTestRun]` and once per feature is `[BeforeFeature]` (as well as more) - in selenium-specflow, this is how I would initiate a webdriver. – RichEdwards Sep 23 '20 at 08:13
  • @DevinGleasonLambert I'm an automation engineer by trade and I'm sat on several examples of using the `hooks` bidings. If this is what you want let me know and i'm happy to drop some same code into an a real answer (and not just a comment) :-) – RichEdwards Sep 23 '20 at 08:15
  • I will share my answer shortly - I ended up rolling my own Singleton (https://csharpindepth.com/articles/singleton) then am calling it from BDD as well as Hooks, but the Singleton ensure that it is only instantiated once. I still would like opportunity to call it somewhere earlier, as the first load could add some extra time to the first person to try to access. – Devin Gleason Lambert Sep 23 '20 at 13:25
  • Where do you need to access your dictionary from? Step definitions? – Greg Burghardt Sep 23 '20 at 15:55
  • Is there a reason why you want only one instance of the dictionary? Does it take up a large amount of memory? – Greg Burghardt Sep 25 '20 at 11:54
  • Hey sorry, adding more details onw – Devin Gleason Lambert Sep 28 '20 at 21:29

1 Answers1

0

In SpecFlow how can I share data between steps/features? appears to be a duplicate, but because of its age and lack of code examples I decided not to mark this question as a duplicate.


Constraint 2: There is no dependency injection on the project, and I am looking for the equivalent of an application state event.

You say there is no dependency injection, but since you are using SpecFlow you have the BoDi dependency injection framework already. Furthermore, SpecFlow already comes with its own way of sharing data between steps in a thread safe manner: the ScenarioContext object.

You need only declare the ScenarioContext object as a parameter in the step definition constructor (see Context Injection), assign it to a field, then use it in your steps. You can do this in multiple classes to share data (see Sharing Data between Bindings):

[Binding]
public class LoginSteps
{
    private readonly ScenarioContext scenario;

    public LoginSteps(ScenarioContext scenario)
    {
        this.scenario = scenario;
    }

    [Given(@"User is logged in as ""(.*)""")]
    public void GivenUserIsLoggedInAs(string username)
    {
        // log user in
        scenario["LoggedInUser"] = username;
    }
}

[Binding]
public class SomeOtherStepDefinitionFile
{
    private readonly ScenarioContext scenario;

    public SomeOtherStepDefinitionFile(ScenarioContext scenario)
    {
        this.scenario = scenario;
    }

    [When(@"some other step requiring current user")]
    public void WhenSomeOtherStepRequiringCurrentUser()
    {
        var currentUsername = (string)scenario["LoggedInUser"];
    }
}

The ScenarioContext object gets passed around as constructor arguments, and one object is initialized for each test. This makes the ScenarioContext object thread safe, and enables parallel execution of SpecFlow tests, which is what you are really after.

Greg Burghardt
  • 17,900
  • 9
  • 49
  • 92