I would like to create an NHibernate session factory once at the start of a SpecFlow test run, and then access it in individual step definitions to call OpenSession() on it.
It seems like a [BeforeTestRun] hook would be the best place to set up the session factory. However I am struggling to see how I can store the session factory and then retrieve it in a particular step definition (most likely part of a Background section) in order to get a session and insert some data.
I tried using the SpecFlow container, as follows:
[Binding]
public class NhDataSupport
{
private readonly IObjectContainer objectContainer;
public NhDataSupport(IObjectContainer objectContainer)
{
this.objectContainer = objectContainer;
}
[BeforeTestRun]
public void InitializeSessionFactory()
{
var sessionFactory = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2012.ConnectionString(c => c.FromConnectionStringWithKey("SqlServerDataTesting")))
.Mappings(cfg =>
cfg.FluentMappings.AddFromAssemblyOf<HostMap>()
)
.BuildSessionFactory();
objectContainer.RegisterInstanceAs<ISessionFactory>(sessionFactory);
}
}
...so that other [Binding] classes could be passed the session factory via constructor injection, I hoped. But this gets a
System.Reflection.TargetException, Non-static method requires a target.
I'm guessing that's because (as I learned from the SpecFlow docs), the method [BeforeTestRun] is applied to must be static.
Is there a way of achieving this, configuring the SessionFactory once but calling OpenSession on it from other Binding classes? I don't want to build the session factory for every scenario, as this is an expensive operation.
The following works.
[Binding]-annotated class. [BeforeTestRun], do the work (in my case building the SessionFactory) and assign the result to the static field. [BeforeScenario], register the static field instance with the container.Not sure if it's best practice, but it does work.
[Binding]
public class DataHooks
{
private readonly IObjectContainer objectContainer;
private static ISessionFactory sessionFactory;
public DataHooks(IObjectContainer objectContainer)
{
this.objectContainer = objectContainer;
}
[BeforeTestRun]
public static void SetupNhibernateSessionFactory()
{
sessionFactory = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2012.ConnectionString(c => c.FromConnectionStringWithKey("SqlServerDataTesting")))
.Mappings(cfg =>
cfg.FluentMappings.AddFromAssemblyOf<HostMap>()
)
.BuildSessionFactory();
}
[BeforeScenario]
public void BeforeScenario()
{
objectContainer.RegisterInstanceAs<ISessionFactory>(sessionFactory);
}
}
The session factory is then available in any [Binding]-annotated class via constructor injection of ISessionFactory.
You could do something like this:
public class SessionFactoryHolder
{
private static ISessionFactory sessionFactory;
public static void SetupNhibernateSessionFactory()
{
sessionFactory = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2012.ConnectionString(c => c.FromConnectionStringWithKey("SqlServerDataTesting")))
.Mappings(cfg => cfg.FluentMappings.AddFromAssemblyOf<HostMap>() )
.BuildSessionFactory();
}
public ISessionFactory SessionFactory
{
get { return sessionFactory; }
}
}
[Binding]
public class Binding
{
[BeforeTestRun]
public static void SetupNhibernateSessionFactory()
{
SessionFactoryHolder.SetupNhibernateSessionFactory();
}
}
Now you can access the SessionFactory when you let SpecFlow inject the SessionFactoryHolder via constructor.
It is similar to @ngm solution, but you can spare to get the "internal" IObjectContainer from SpecFlow.
See here http://www.specflow.org/documentation/Context-Injection/ for more infos about context injection in SpecFlow.
Notice: code written by head, not tried to compile, so there could be typos.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With