From personal experience: they have made a huge step forward from v6 to v7, but there is still to go. I would try to focus on areas where more "testable API" is used and reconsider once again about possible tests where "less testable API" are used. I would try to figure out what excatly should be tested and where it's worth to add mocks in order to get tests running. Maybe I would need to test ValidateUser() method out of LoginExisting() context and do more granular test in order to get around localization service in this case.
In summary: yes, there is still path to go and more testable / mockable / shimable / whatver API should be added, but on the another hand I can truly understand EPiServer that this is a long way to go with all the consequences around.
And btw, when you say "static methods ... are hard to test and unmockable" is it so that you cannot change behavior of the static properties / methods? You may look into Fakes library - it has interesting capabilities:
[TestMethod]
public void TestMethod1()
{
using (ShimsContext.Create())
{
var mock = new Mock<LocalizationService>(null);
mock.Object.Arrange(s => s.GetString(Arg.AnyString)).Returns(Arg.AnyString);
ShimLocalizationService.CurrentGet = () => mock.Object;
var result = CallSampleMethod();
Assert.Equals(result, "sample-key");
}
}
private string CallSampleMethod()
{
return LocalizationService.Current.GetString("sample-key");
}
Hi Valdis,
thank you for your answer. Yeah, I was lurking around and gathering information about the Microsoft Fakes framework as the solution to my issues. Unafortunately it is only available in Visual Studio Premium and my company only has available the Professional version.
I guess I'll have to stick to wrappers over the API and refactor existing code to make it more testable until EPiServer becomes more Test-friendly :)
And what about integration tests on a real environment? How can I set up all the EPiServer initialization from a unit test project?
When it comes to integration tests you have to make sure that test project has all the configs EPiServer will need at some point. The best way we found in actually decomplie EPiServer.Global type and look for hints how to setup required configuration. You just need to pull in stuff you need.
For instance DDS init:
var container = new Container();
var factory = new SqlDatabaseFactory();
container.Configure(ce => ce.For<IDatabaseHandler>().Use(factory.CreateDefaultHandler));
ServiceLocator.SetLocator(new StructureMapServiceLocator(container));
DataInitialization.InitializeFromCode(container, factory, null);
var store = typeof(SampleModel).GetStore();
Part of Commerce init:
var container = new Container();
var context = new ServiceConfigurationContext(HostType.Service, container);
new CommerceInitialization().ConfigureContainer(context);
var factory = new SqlDatabaseFactory();
container.Configure(ce => ce.For<IDatabaseHandler>().Use(factory.CreateDefaultHandler));
var locator = new StructureMapServiceLocator(container);
ServiceLocator.SetLocator(locator);
Pull stuff you need. Look for various implementations of IInitializableModule or IConfigurableModule.
And when it comes to Fakes - AFAIK assembly generation is available in Premium, you can generate those and use in Professional, but yes - Fakes should be available to everyone, otherwise Microsoft is preventing to use some sort of Tdd disciplines.. :)
I don't think that installing VS Premium just for Fakes is an option to me... So I think I'll stick with refactor and making code somewhat more testable and maintainable.
About the integration tests, I'll try your suggestion and come back with the results.
Thank you very much for your support
Use ex. Moq for mocking. remember that you can also mock the servicelocator, so if you make sure you use the EPiServer.ServiceLocation.ServiceLocator.Current for all your service instantiation, you can atleast mock most calls to episerver functionality when you test your code.
Hi there,
I have several doubts about how to create Unit tests and integration tests in EPiServer, but first let me put you in context.
last weeks I've been messing around with the EPiServer CMS and Commerce Sample Sites. In an effort to learn about EPiServer, and as the course I took from EPiServer was oriented towards Webforms, we decided to use it and try to familiarize as much as possible. Additionally, in order to provide testability in Webforms, we have adapted the code to MVP architecture by using a variant of the EPiMVP framework proposed by Joel Abrahamsson, so we could create testable test and "literally" copy and paste code from the code behind of views to presenters.
As an example, here's one piece of code I refactored into a presenter and then tried to Unit Test. It can be found in \wwwroot\Templates\Sample\Units\Security\Login.ascx.cs from the Commerce Sample Site:
protected void LoginExisting(object sender, LoginArgs args) {
var username = EmailAddress_ExistingId.Value;
var password = Password_ExistingId.Value;
bool remember = !String.IsNullOrEmpty(Request.Form["RememberMe"]);
if (username == null || !Membership.ValidateUser(username, password))
{
SignInFailureText.Text = "Login failed. Please make sure username and password are correct.";
return;
}
var profile = SecurityContext.Current.CurrentUserProfile as CustomerProfileWrapper;
if (profile == null)
{
throw new NullReferenceException("profile");
}
var accountState = profile.State;
if (accountState == 1 || accountState == 3)
{
SignInFailureText.Text = LocalizationService.Current.GetString("Sample/Validation/AccountLocked");
return;
}
CreateAuthenticationCookie(username, AppContext.Current.ApplicationName, remember);
Context.RedirectFast("/Self-Service/Account-Info/");
}
As you can see, this code has several "smells" which makes it untestable. First, it uses a hefty amount of static methods from EPiServer Framework like LocalizationService, Membership or SecurityContext, which are hard to test and unmockable. In order to do so, I had to create some wrappers around these static classes, refactor the ExistingLogin method so I could extract interfaces which then I could Mock (I use Moq as mock tool). However, EPiServer is pleeeenty of static functions and classes, and creating wrappers for each and every one of them can be very time consuming.
My first question is: how can I effectively Unit Test with EPiServer? Which facilities does it provide? Any tips on this matter?
Then, I would also be interested in running these tests on an integrated EPiServer environment which could be executed automatically, with all Contexts like the database, security, localization, contentloader... up so I can call some methods and see how they work on a real environment. Yet I don't know how to set up the environment for performing integration tests. Can anyone enlighten me somehow?
Thank you and kind regards,
David