November Happy Hour will be moved to Thursday December 5th.

Integration tests fails due to initialization quirks related to test framework

Vote:
 

Hi,

I followed the guidelines by @henriknystrom at https://thisisnothing.nystrom.co.nz/2017/12/05/integration-testing-with-episerver/ refered to in this closed thread: https://world.episerver.com/Modules/Forum/Pages/Thread.aspx?id=185646

I'm trying out some Integration testing, using NUnit 3 and SpecFlow, IIS Express and Selenium.

When calling:

_engine = new InitializationEngine((IEnumerable)null, HostType.TestFramework, new AssemblyList(true).AllowedAssemblies);
_engine.Initialize();

I get a failure that seems to be related to this: https://github.com/Microsoft/vstest/issues/1417

So, basically I should not be reflecting types from Microsoft.VisualStudio.TestPlatform.ObjectModel.

Therefore I tried something like this:

var data = Assembly.GetAssembly(typeof(EPiServer.Data.DataInitialization));
var events = Assembly.GetAssembly(typeof(EPiServer.Events.EventMessage));
var framework = Assembly.GetAssembly(typeof(EPiServer.Framework.EnvironmentOptions));
var epiServer = Assembly.GetAssembly(typeof(EPiServer.DeleteContentEventArgs));
var epiServerEnterprise = Assembly.GetAssembly(typeof(EPiServer.Enterprise.ContentExportedEventArgs));
var structureMap = Assembly.GetAssembly(typeof(EPiServer.ServiceLocation.StructureMapServiceLocator));

var _engine = new InitializationEngine((IEnumerable)null, HostType.TestFramework,
  new List {
  data,
  events,
  framework,
  epiServer,
  epiServerEnterprise,
  structureMap
  }
);

_engine.Initialize();

But it´s not getting me the whole way, some error about no connectionstrings is found etc. (I have the connectionstring in my app.config, and I have a InitializationModule setting the connectionstring up configuring DataAccessOptions)

Does anyone know exactly which assemblies are needed to setup EPi CMS for testing or how I could use new AssemblyList(true).AllowedAssemblies and instead exclude Microsoft.VisualStudio.TestPlatform.ObjectModel?

Guess things starts working when all needed assemblies are loaded and the initialization modules are run correctly, without some buggy assembly comes interfering and ruining my geeky party. @henriknystrom - can you please shed some light on this for me!? ;)


#198643
Edited, Nov 02, 2018 0:08
Vote:
 

It is code in assembly EPiServer.Framework.AspNet that is responsible to read data from config and populate DataAccessOptions. So either you add that assembly to your list or you can have a module in your integration test project with code as:

  public void ConfigureContainer(ServiceConfigurationContext context)
        {
            context.Services.Configure<DataAccessOptions>(o =>
            {
                o.CreateDatabaseSchema = true;
                o.UpdateDatabaseSchema = true;
                o.SetConnectionString(<yourconnectionstring>);
            });

        }
#198652
Nov 02, 2018 8:57
Vote:
 

Thank you!

I have such a InitializationModule — without the options, trying an reset before, restore after approach (such as https://blog.mathiaskunto.com/2016/11/25/keeping-reliable-test-data-in-episerver-content-database-for-automated-ui-tests/), testing the Alloy site as a starter.

It doesn’t seem to be initiated though.

Will look into the other option when I can. 👍

#198668
Edited, Nov 02, 2018 11:37
Vote:
 

I made some progress, regarding getting forward from the error I had, when adding EPiServer.Framework.AspNet, but then I hade to add another assembly for the next error, and another assembly etc, and the more I added, the more complex things got, and I still ended up with the same issue, the InitializableModule isn't loaded etc.!

I ended up with this:

var data = Assembly.GetAssembly(typeof(EPiServer.Data.DataInitialization));
var events = Assembly.GetAssembly(typeof(EPiServer.Events.EventMessage));
var framework = Assembly.GetAssembly(typeof(EPiServer.Framework.EnvironmentOptions));
var frameworkAspnet = Assembly.GetAssembly(typeof(EPiServer.Framework.Web.AspNetAntiForgery));
var epiServerCmsCore = Assembly.GetAssembly(typeof(EPiServer.DeleteContentEventArgs));
var cmsAspnet = Assembly.GetAssembly(typeof(EPiServer.Web.InitializationModule));
var epiServerEnterprise = Assembly.GetAssembly(typeof(EPiServer.Enterprise.ContentExportedEventArgs));
var structureMap = Assembly.GetAssembly(typeof(EPiServer.ServiceLocation.StructureMapServiceLocator));
var applicationModules = Assembly.GetAssembly(typeof(EPiServer.Personalization.VisitorGroups.VisitorGroupOptions));
var linkAnalyzer = Assembly.GetAssembly(typeof(EPiServer.LinkAnalyzer.LinkValidatorOptions));

var _engine = new InitializationEngine((IEnumerable<IInitializableModule>)null, HostType.TestFramework,
  new List <Assembly> {
data,
events,
cmsAspnet,
framework, // crucial?
frameworkAspnet,
linkAnalyzer,
applicationModules,
epiServerCmsCore,  // crucial?
  epiServerEnterprise,
structureMap  // crucial?
});
_engine.Initialize();

This leads me (as always) wanting the minimal setup possible, preferable only EPiServer.Framework, EPiServer.CMS.Core and EPiServer.ServiceLocation.StructureMap.

The issue I'm back to now is why my InitializableModule isn't executed before the errors about the connectionstring or that the database does not exist in the bin directory (the InitializableModule should correct that if loaded correctly):

    [InitializableModule]
[ModuleDependency(typeof(CmsCoreInitialization))]
public class Initialization : IConfigurableModule
{
public void ConfigureContainer(ServiceConfigurationContext context)
{
// Configure the database access
context.Services.Configure<DataAccessOptions>(o =>
{
//getting the physical path of Alloy-web-root
var webAppPath = IISExpressHostFactory.GetApplicationPath();
var originalConnectionString = ConfigurationManager.ConnectionStrings["EPiServerDB"].ConnectionString;
  var absolutePathConnectionString = originalConnectionString.Replace("|DataDirectory|", Path.Combine(webAppPath, "App_Data") + "\\");
o.SetConnectionString(absolutePathConnectionString);
  // in my case: "Data Source=(LocalDb)\MSSQLLocalDB;AttachDbFilename=C:\EPiServer\TestDrive\TestDrive.Web\App_Data\EPiServerDB_1fe05723.mdf;Initial Catalog=EPiServerDB_1fe05723;Connection Timeout=60;Integrated Security=True;MultipleActiveResultSets=True"
});
}

void IInitializableModule.Initialize(InitializationEngine context) { }
void IInitializableModule.Uninitialize(InitializationEngine context) { }
}

Isn't the InitializationEngine and related assemblies supposed to run the InitializableModule:s accordingly if this module is existing in the running assembly without any more configuration?

My only need is to create Pages and Blocks by code using IContentRepository, on a database that is not hard coded but changed in an InitializableModule. So I need only the basic stuff.

I'm I right that we no longer have to put most of the web.config parts in app.config when initializing this way, I'm on the right path?

The provided code in this comment is the only code I wrote for the EPi initialization part  is there anything obvious I might be missing?

#198691
Edited, Nov 02, 2018 19:55
Vote:
 

If you pass in assemblies to InitializationEngine then it will only scan those assemblies for modules. So you should add your test assembly to the scanning. E.g like this.GetType().Assembly 

#198693
Nov 02, 2018 20:05
Vote:
 

Ah, that's silly by me. I'll try that right away... ;)

#198694
Nov 02, 2018 20:14
Vote:
 

Thank you!

At last I got things to work!

var data = Assembly.GetAssembly(typeof(EPiServer.Data.DataInitialization));
var events = Assembly.GetAssembly(typeof(EPiServer.Events.EventMessage));
var framework = Assembly.GetAssembly(typeof(EPiServer.Framework.EnvironmentOptions));
var epiServerCmsCore = Assembly.GetAssembly(typeof(EPiServer.DeleteContentEventArgs));
var epiServerEnterprise = Assembly.GetAssembly(typeof(EPiServer.Enterprise.ContentExportedEventArgs));
var structureMap = Assembly.GetAssembly(typeof(EPiServer.ServiceLocation.StructureMapServiceLocator));
var applicationModules = Assembly.GetAssembly(typeof(EPiServer.Personalization.VisitorGroups.VisitorGroupOptions));
//var frameworkAspnet = Assembly.GetAssembly(typeof(EPiServer.Framework.Web.AspNetAntiForgery));
//var cmsAspnet = Assembly.GetAssembly(typeof(EPiServer.Web.InitializationModule));

//var linkAnalyzer = Assembly.GetAssembly(typeof(EPiServer.LinkAnalyzer.LinkValidatorOptions));

var _engine = new InitializationEngine((IEnumerable<IInitializableModule>)null, HostType.TestFramework,
new List <Assembly> {
data,
events,
framework,
applicationModules,
epiServerCmsCore,
epiServerEnterprise,
structureMap,
Assembly.GetExecutingAssembly(),
//frameworkAspnet,
//linkAnalyzer,
//cmsAspnet,

});
_engine.Initialize();

The above was the minimal working setup I could come up with, had to remove some parts but keep others, by different reasons. Like virtualPath could not be null etc. (probably a EPiServer.Framework.AspNet related issue)

Now I only have test assertion issues, the things I didn't finnish due to the problems initiating the framework. 👍

Thank you Johan for your support, I've learned what I can do and that this code actually works in my setup. Hopefully someone else with similar issues can find some angles or solutions to their problems.

Now I can continue testing how I would like to improve the workflow and quality work when building EPiServer webs.

#198697
Edited, Nov 02, 2018 21:41
Vote:
 

I thought it might be a better way to do blacklist instead of a whitelist.

One of the issues for me was getting a lot of dependencies I didn't want in my bin-folder since I referenced the Alloy-project and got all its dependencies.

At least a blacklist gives me more control of what I am excluding.

var blacklist = new[] {
typeof(NUnit.Framework.ApartmentAttribute).Assembly,
Assembly.Load("NUnit3.TestAdapter"),
Assembly.Load("EPiServer.Find"),
Assembly.Load("EPiServer.Find.Cms"),
Assembly.Load("EPiServer.Find.Framework"),
Assembly.Load("EPiServer.Find.UI"),
Assembly.Load("EPiServer.Framework.AspNet"),
Assembly.Load("EPiServer.Cms.AspNet"),
Assembly.Load("EPiServer.Cms.Shell.UI"),
Assembly.Load("EPiServer.LinkAnalyzer"),
Assembly.Load("EPiServer.Shell"),
Assembly.Load("EPiServer.Shell.UI"),
Assembly.Load("EPiServer.Cms.TinyMce"),
Assembly.Load("EPiServer.Cms.UI.AspNetIdentity"),
Assembly.Load("EPiServer.UI"),
typeof(Web.Global).Assembly,
}.ToEnumerable();

var _engine = new InitializationEngine(
(IEnumerable<IInitializableModule>)null,
HostType.TestFramework,
new AssemblyList(true).AllowedAssemblies.Except(blacklist));
_engine.Initialize();

Do you think this is a more future safe way of doing things?

#198705
Edited, Nov 03, 2018 11:47
Vote:
 

blacklisting may bypass stuff that you actually don't want to see in your assembly list. think why Microsoft gives you "empty" aspnet runtime in netcore apps for instance - where everything you want has to be explicitly added?

#198837
Nov 07, 2018 14:16
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.