MVC 7.5 and TDD

Vote:
 

Dear experts,


I have just started recently using episerver and have a website developed in version 7.0. I would like to upgrade it to 7.5. I have few questions which may sound silly but I will really appreciate if you reply.

1. Can we upgrade the website using deployment center 7.0 to 7.5 easy? Our website is based on version 7.0 webforms, and we need to convert it to 7.5 MVC. We know it is completely different that is why not too much sure. What steps do we need to take or simply re-write the functionality again?

2. We need to implement TDD in it, what are the testing framework and mocking framework that we should select?  I have more interest using MSTest and Unit application block as mocking framework, is it ok to use them? I have been using them since but without episerver. Please suggest. 

3. Can I get some basic structure of episerver 7.5 MVC website with unit testing implemented? All I need few examples to see how we implement. Sample code will be much appreciated.

4. Is episver CMS 7.5 fully compliant with unit testing. I understand the previous versions you cannot.? Please confirm.


Once again, many thanks for reading my post.

Warm regards,
Emma

#114885
Dec 29, 2014 12:40
Vote:
 

My recommendation is like this:

1: Upgrade it with deployment center and when you have a working website with webforms start to convert it to MVC part by part. We have a website that we did just like you talking about and some part of it is still webform. The thing to think of is that when doing the accual deployment you will do it in a couple of special steps. You will do like this to have the live site working all the time

    * Backup the live database
    * Restore it in the local/development enviroment and also take a copy of the code from the live site and change the connectionstrings so the local site points to the local database
    * Set up a local IIS that points to the local site (the copied one) and make shore that it works
    * Run deployment center and upgrade the local site with database
    * When that is done, start the vpp migration tool and point it the the local upgraded site and migrate the vpp
    * After that is done you can deploy your new code that use MVC to the local site and see that it all works.
    * After that you can backup the local database and restore it to the live enviroment and copy the new files to the live IIS-site.

You will need to rewrite the most of the code since MVC and Webform are very different, but if you have built up the site with so little functionality as possible in the webform the job will not be so much but if you have all logic in the aspx.cs-files you have a huge job infront of you

2: We use nUnit and FakeItEasy, but xUnit and nMock or any other framwork works fine also, but I like FakeItEasy

3: Create a alloy site with the latest VS integration tool, it will give you a pretty ok solution, but not with any test. I can see if I find any when I get back from beeing away from work.

4: EPiServer is not fully TDD friendly if you look at it in the most extreme way, there are not Interface for everything but for the most importent things. You will be able to do a lot, hopefully so that you will be pleased but you will see that there are places where you can not mock away all that you want.

Good luck!

#114893
Dec 30, 2014 13:30
Vote:
 

Thank you Henrik so much for your valuable and detailed response.This is what I require at the moment.

For point number 2:- I will wait for the unit test examples, would really be appreciated whenever you send it.  You can also post it to me on my direct email.:- emmathomasmcad@outlook.com

I have two more questions:-

1. I have used nuget to get the latest 7.5 templates. Is it ok? Do i still need latest VS integration tool>

2. I am trying to implement structure map in my solution to inject dependency using it, but facing few problems, I am putting my code under global.asax, is it right to do? An example code/solution will again be highly appreciated.


Warm regards,
Emma

#114898
Dec 30, 2014 15:59
Vote:
 

You do not need the VS ingegration tool, NuGet is the way to get the latest versions.

We also using structuremap, will give you a example when I get to my work computer

#114906
Dec 31, 2014 8:35
Vote:
 

For the structuremap part, what problem do you have? Any errormessage?

We use it like this:

In global.asax.cs

protected void Application_Start()
{
            .........

            Bootstrapper.SetupLegacyIoC();

             ....
}

The bootstraper class looks like this:

using System.Web.Mvc;
using EPiServer.Framework;
using EPiServer.Framework.Initialization;
using EPiServer.ServiceLocation;
using StructureMap;

namespace CUSTOMER.Framework.Initialization
{

    [ModuleDependency(typeof(ServiceContainerInitialization))]
    [InitializableModule]
    public class Bootstrapper : IConfigurableModule
    {
        public void ConfigureContainer(ServiceConfigurationContext context)
        {
            context.Container.Configure(ConfigureContainer);

            DependencyResolver.SetResolver(new StructureMapDependencyResolver(context.Container));
        }

        private static void ConfigureContainer(ConfigurationExpression container)
        {
            container.AddRegistry(new PersistanceRegistry());
        }

        public void Initialize(InitializationEngine context)
        {
        }

        public void Uninitialize(InitializationEngine context)
        {
        }

        public void Preload(string[] parameters)
        {
        }

        public static void SetupLegacyIoC()
        {
            ObjectFactory.Initialize(scanner =>
            {
                scanner.AddRegistry(new PersistanceRegistry());
            });
        }    
    }
}

And the PersistanceRegistry looks like this:

using System.Configuration;
using NHibernate;
using StructureMap.Configuration.DSL;

namespace CUSTOMER.Persistance.Configuration
{
    public class PersistanceRegistry : Registry
    {
        public PersistanceRegistry()
        {
            Scan(x =>
                     {
                         x.AssemblyContainingType<LogRepository>();
                         x.WithDefaultConventions();
                     }
                );

           
            For<ISessionFactory>().Use(x => SessionFactoryManager.Factory);
            // Will cache over a http request, or fallback to thread if no web context is available
            // which is the case with episerver scheduled jobs triggered by schedulation (not manueally via web ui)
            For<ISession>().HybridHttpOrThreadLocalScoped().Use(x => x.GetInstance<ISessionFactory>().OpenSession());
           
        }
    }
}

I have removed some code to make the example a little simple but it shows for example how to setup nHibernate session and how to scan for all assemblys for a specific type.

Hope this gives a litte directorion/help to you.

#114922
Jan 02, 2015 12:40
Vote:
 

For the unit test information, Ted Nyberg has written a blog post on how to do it in EPiServer 7 that is great, take a look at this blog and you will find good examples

http://tedgustaf.com/blog/2013/10/unit-testing-in-episerver-7/

#114923
Jan 02, 2015 12:43
Vote:
 

Thanks very much Henrik, and sorry that I could not post you earlier.

I think I am slightly confused with Structuremap usage, perhaps because i have just started using StructureMap as episerver uses it. Correct me if I am wrong.

1. Can I instead of using StructureMap, use UnityApplication Block or Ninject?
2. Is structure map compulsary step for episerver applications because episerver supports it?
3. Can you explain me some example usage of structure map using episerver? 

4. For unit testing, I have seen that Ted blog post, thanks for that.

I am confused how to use it for my day to day normal scenarios and complex episerver pages.
I would love to see few more examples using unit test. Do you have a small project which uses some unit test example specific to episerver?. I am sorry or being persistent for asking the small project as I really want to know with episerver how can you do it. what parts where I can apply unit test and how. You can also send me to my email account as I mentioned.

Like for example,

a.  If I have a simple episerver page in MVC using PageController. say HomePageController.zHow do I unit test that HomePageController using any testing framework or Ms Test framework specially in terms of episerver?

b. And if I have block using blockcontroller, what parts where I can apply unit test and How can I do the unit test on that specfic page?/
Sorry if I sound so dumb and silly,  but please if you can reply

Many thanks once again and belated happy new year,

note: I am just updating it to get replies to my query please.


I look forward to hearing from you soon.

Kind regards,
Emma

#114933
Edited, Jan 03, 2015 17:46
Vote:
 
#114964
Jan 06, 2015 11:59
Vote:
 

Sorry for not getting back to you. We have had a crazy amount of days of work here in sweden so the focus has not been in coding these days :-)

I think you can use any other framework instead of structuremap for your own stuff, we use structuremap for all EPiServer project so I have not tried it, but think it works.

An exampel of usage exists in the alloy mvc template that you can install with the Visual studio Addon. 

In that they show how to set up dependencyresolver in this file: DependencyResolverInitialization, se here:

using System.Web.Mvc;
using EPiServer.Framework;
using EPiServer.Framework.Initialization;
using EPiServer.ServiceLocation;
using TinyMceInCustomProperties.Business.Rendering;
using TinyMceInCustomProperties.Helpers;
using EPiServer.Web.Mvc;
using EPiServer.Web.Mvc.Html;
using StructureMap;

namespace TinyMceInCustomProperties.Business.Initialization
{
    [InitializableModule]
    [ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
    public class DependencyResolverInitialization : IConfigurableModule
    {
        public void ConfigureContainer(ServiceConfigurationContext context)
        {
            context.Container.Configure(ConfigureContainer);

            DependencyResolver.SetResolver(new StructureMapDependencyResolver(context.Container));
        }

        private static void ConfigureContainer(ConfigurationExpression container)
        {
            //Swap out the default ContentRenderer for our custom
            container.For<IContentRenderer>().Use<ErrorHandlingContentRenderer>();
            container.For<ContentAreaRenderer>().Use<AlloyContentAreaRenderer>();

            //Implementations for custom interfaces can be registered here.
        }

        public void Initialize(InitializationEngine context)
        {
        }

        public void Uninitialize(InitializationEngine context)
        {
        }

        public void Preload(string[] parameters)
        {
        }
    }
}

And then, they show how to use it in a controller in example this file: SearchPageController, se here:

using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using EPiServer.Core;
using EPiServer.Framework.Web;
using EPiServer.Search;
using TinyMceInCustomProperties.Business;
using TinyMceInCustomProperties.Models.Pages;
using TinyMceInCustomProperties.Models.ViewModels;
using EPiServer.Web;
using EPiServer.Web.Hosting;
using EPiServer.Web.Mvc.Html;
using EPiServer.Web.Routing;

namespace TinyMceInCustomProperties.Controllers
{
    public class SearchPageController : PageControllerBase<SearchPage>
    {
        private const int MaxResults = 40;
        private readonly SearchService _searchService;
        private readonly ContentSearchHandler _contentSearchHandler;
        private readonly UrlResolver _urlResolver;
        private readonly TemplateResolver _templateResolver;

        public SearchPageController(
            SearchService searchService, 
            ContentSearchHandler contentSearchHandler, 
            TemplateResolver templateResolver,
            UrlResolver urlResolver)
        {
            _searchService = searchService;
            _contentSearchHandler = contentSearchHandler;
            _templateResolver = templateResolver;
            _urlResolver = urlResolver;
        }

        [ValidateInput(false)]
        public ViewResult Index(SearchPage currentPage, string q)
        {
            var model = new SearchContentModel(currentPage)
                {
                    SearchServiceDisabled = !_searchService.IsActive,
                    SearchedQuery = q
                };

            if(!string.IsNullOrWhiteSpace(q) && _searchService.IsActive)
            {
                var hits = Search(q.Trim(), 
                    new [] { ContentReference.StartPage, ContentReference.GlobalBlockFolder, ContentReference.SiteBlockFolder }, 
                    ControllerContext.HttpContext, 
                    currentPage.LanguageID).ToList();
                model.Hits = hits;
                model.NumberOfHits = hits.Count();
            }

            return View(model);
        }

        /// <summary>
        /// Performs a search for pages and media and maps each result to the view model class SearchHit.
        /// </summary>
        /// <remarks>
        /// The search functionality is handled by the injected SearchService in order to keep the controller simple.
        /// Uses EPiServer Search. For more advanced search functionality such as keyword highlighting,
        /// facets and search statistics consider using EPiServer Find.
        /// </remarks>
        private IEnumerable<SearchContentModel.SearchHit> Search(string searchText, IEnumerable<ContentReference> searchRoots, HttpContextBase context, string languageBranch)
        {
            var searchResults = _searchService.Search(searchText, searchRoots, context, languageBranch, MaxResults);

            return searchResults.IndexResponseItems.SelectMany(CreateHitModel);
        }

        private IEnumerable<SearchContentModel.SearchHit> CreateHitModel(IndexResponseItem responseItem)
        {
            var content = _contentSearchHandler.GetContent<IContent>(responseItem);
            if (content != null && HasTemplate(content) && IsPublished(content as IVersionable))
            {
                yield return CreatePageHit(content);
            }
        }

        private bool HasTemplate(IContent content)
        {
            return _templateResolver.HasTemplate(content, TemplateTypeCategories.Page);
        }

        private bool IsPublished(IVersionable content)
        {
            if (content == null)
                return true;
            return content.Status.HasFlag(VersionStatus.Published);
        }

        private SearchContentModel.SearchHit CreatePageHit(IContent content)
        {
            return new SearchContentModel.SearchHit
                {
                    Title = content.Name,
                    Url = _urlResolver.GetUrl(content.ContentLink),
                    Excerpt = content is SitePageData ? ((SitePageData) content).TeaserText : string.Empty
                };
        }
    }
}

In that file, they instance for example the searchService as a private property and then take it in the constructor as a parameter and doing like that will make structuremap understand that it should pass along a instace of that object (please correct me if I am wrong anyone that know this better than me).

For the testing things, first I must say that I am not a TDD beliver, I thinks it often adds a extra layer that needs to been kept updated and does not add a lot. There are examples of good usage but for a website it is often not the best solution. I am more a beliver of BDD where you accually test the things the customer thinks is the most important things and I am planning a blog post on doing BDD with SpecFlow. There are a good post on it here already: http://www.popkram.com/blog/2013/01/ui-testing-episerver-specflow/

I have looked and I do not have any good example of unit testing, we do it in the projects but there are no simple example. I hope that some other person here can help with a good example.

#114978
Jan 07, 2015 7:33
Vote:
 

Thanks Henrik or your great post and for the level of help you are providing and I highly appreciate this.
I will look into the example that you mentioned for dependency injections as soon as I get back to office, hopefully in detail. 

For BDD:- I will also explore this BDD concepts.

For TDD:- I would stll like to request you for one thing, as you said you have projects where you are using it. I simply need to know a bit of varietions how do you test. If you do not have simple examples, can you screengrab some of your usage scenario like the same you did for dependency injections. It is very helpul i must say. You can also send me those screengrabs to my email account. If it is too much I am asking, than I am sorry, ignore my posts. I will wait for anyother seniors to come back to me on this.. I will appreciate highly. 

Many thanks once again
Best regards

#114998
Edited, Jan 07, 2015 16:09
Vote:
 

I hope this examples helps you a little bit.

In one project that is a Intranet we have a articlepage and for that a articlecontroller. In that we are using structuremap to resolve some dependency's into it. The controller looks something like this (I have stripped the code to make it smaller and more easy so it is not complete)

using System.Web.Mvc;
using EPiServer.Web.Routing;
using SITE.Core.ResourceAccess.Persons
using SITE.Web.Framework;
using SITE.Web.Models.Pages;
using SITE.Web.Models.ViewModels;

namespace SITE.Web.Controllers
{    
    public class ArticlePageController : ContentpageBaseController<ArticlePage>
    {
        public ArticlePageController(IPersons persons, IDocumentManager documentManager, ICurrentUser currentUser, UrlResolver urlResolver) :
            base(persons, documentManager, currentUser, urlResolver)
        {
        }
        public ViewResult Index(ArticlePage currentPage)
        {            
            var model = new ContentpageViewModel(currentPage);                 
            return View(model);
        }        
    }
}

For this controller we have a couple of test, here are an example of one where we see that the responsibleEditor for the page will be set:

using EPiServer.Web.Routing;
using FakeItEasy;
using SITE.Core.Entities;
using SITE.Core.ResourceAccess.Persons;
using SITE.Web.Controllers;
using SITE.Web.Framework;
using SITE.Web.Models.Pages;
using SITE.Web.Models.ViewModels;
using SITE.Web.UnitTests.TestHelpers;
using NUnit.Framework;

namespace SITE.Web.UnitTests.Controllers
{
    [TestFixture]
    public class ArticlePageControllerTests
    {
        private IPersons _persons;
        private ArticlePageController _controller;

        [SetUp]
        public void Setup()
        {
            _persons = A.Fake<IPersons>();
            _controller = new ArticlePageController(_personregistret, A.Fake<IDocumentManager>(), A.Fake<ICurrentUser>(), A.Fake<UrlResolver>());
        }

        [Test]
        public void Index_WhenThereIsAResponsibleEditor_ResponsibleEditorShouldBeSet()
        {            
            var person = new Person { FirstName = "Firstname", LastName = "LastName" };
            A.CallTo(() => _persons.GetPersonByUserId("testname")).Returns(person);            
            
            
            var result = ControllerHelper.ExecuteAction(() => _controller.Index(new ArticlePage {ResponsibleEditorId = "testname"}));            
            var model = ((ContentPageViewModel) result.Model);

            Assert.That(model.ResponsibleEditor, Is.EqualTo(person));
        }
    }    
}

Here you can see how we use FakeItEasy and Structuremap. The ExecuteAction we have written our own with the insperation from here:

http://www.codeproject.com/Articles/623793/OnActionExecuting-and-OnActionExecuted-in-MVC-unit#_rating

Hope this help you some.

Good luck!

#115084
Jan 09, 2015 7:30
Vote:
 

Hi Henrik,

Sorry for late response, and thanks very much once again for your post. This is great, you are a legend and enough for me to dive into it now.
I am going to use few examples and few with MS Test and come back if I found any problem.


Once again, thanks a million for your help.

Regards,
Emma

#115370
Jan 14, 2015 10:01
Vote:
 

Thanks! Glad that it might help you!

Please mark my answer as a "answer" if you thought it helped you

#115371
Jan 14, 2015 10:03
Vote:
 

Hi Emma and Henrik,

This is an interesting discussion and I'd like to add few things. 

First of all, setting up a dependency resolver in a EPiServer project should apparantly be done in an "InitializableModule" in the way Henrik describes above. Doing it in Global.asax is too late in the application startup pipeline for EPiServer projects so it won't work, eventhough that's a pretty standard pattern in normal MVC projects. Not really a problem if you know about it.

As for selecting a DI framework, I've always being happy with StructureMap and the later versions are now more easy to use, I'd say. The documentation is pretty confusing though...

When it comes to unit testing EPiServer projects it gets more interesting. Personally I'm more into TDD and unit testing than Henrik and EPiServer is really frustrating to work with if you want to do this cleanly.

For example, take such a simple thing as to create an instance of a page and set its name to perform some testing:

public class MyPage : PageData {}
...
var page = new MyPage();
page.PageName = "a";
...

Easy to understand and compiles just fine. But crashes with an exception when executed:

EPiServer.Core.EPiServerException : Property 'PageName' does not exist, can only assign values to existing properties

PageName does not exist? But it compiles?

It turns out that all standard system properties are read-only. There's a trick though, which is to use the "Property" property:

page.Property["PageName"] = new PropertyString("a");

If we now read the value of page.PageName, it will indeed be "a", but it's not very pretty, is it?

And it gets much worse than this when working with Content area properties and things like that. They're lazy loaded and thus require a live data store (i.e. a live EPiServer site) to work, so you can't just create a page instance with the required property value and perform tests. Yes, you can override some of the interfaces that are used but it gets very complicated and requires a lot of knowledge of EPiServer inner workings. Also, the tests are hard to understand and maintain. It's also very difficult to know what interfaces to override since they have chosen the service locator pattern over constructor dependency injection. I'm sure there is a good reason for that but I'm not seeing it. As it is now you have to reverse engineer EPiServer assemblies to see how they work in order solve some of these mocking problems. (If you need to do that then the free Telerik JustDecompile is a very good tool.)

Because of the chosen EPiServer architecture I would say that you're not likely to do well if you focus too much on unit testing. And TDD is almost out of the question for all but non-EPiServer, contained logic (utility logic, integrations, view models, etc are still perfectly testable).

Of course, I could be wrong about this but I've really seen a tendency among to EPiServer developers to focus more on web tests which are more BDD oriented. I think that this is in part due to the difficulty with which you work with TDD and unit testing in these projects. There is much value in BDD and web tests, but I always feel very vulnerable without proper unit test code coverage... Web tests are so much slower and in my opinion more difficult to write. But the biggest problem is that you can get away with very sloppy coding habits and still have passing web tests. If you do TDD you tend to be forced to create a much more componentized and SOLID conformant architecture.

/Emil

#115425
Jan 15, 2015 0:55
Vote:
 

Emil, great feedback and it is very true that you are more into TDD than me :)

The example you give represent how to do it in CMS 6, in 7 and 7.5 you should always use IContent when creating pages or other content, with that you can test and mock the IContentRepository.

#115449
Jan 15, 2015 10:54
Vote:
 

Hi Emil and Henrik,

My previous post was lost for some reason, I am editing just here now
Thanks you for taking time to read this all and to give your valuable suggestions.

I am a bit confused here, please correct me if I am wrong

For DI:-

1. I should rather stick with Structure map as you said it works very well with episerver, which is also a default episerver configuration too rather than using Ninject or Unity Application block?

For Unit Testing:

1. Are you saying, I can use MsTest which works fine with unit testing? or do you recommend to use Nunit or some other framework?

2. When you mentioned about WebTest, I presume that is where you need to automate your testing, correct me if I am wrong?

3. I cannot unit test episerver functionality straightaway but can do non-episerver stuff easily.  So should I avoid doing unit testing on module which involves Episerver and just do for without one only as a best approach?

4. For example i have a view model and it has episerver depenencies in some of the functions and some of the functions does not. So I should only unit test for the module without episerver?
If you can post few code examples with and without episerver along with it will again be highly appreciated.

For mocking framework

What do you recommend for mocking framework to use with episerver?

Sorry for asking lots of question, but I want to use the best approach as I am new to it.

Many thanks,

Emma

#115527
Edited, Jan 15, 2015 13:41
Vote:
 

Hi again Emma,

DI:

1: Yes, you should be okay with StructureMap (I use it all the time). There is no need to switch to something else if you don't have any special needs.

Unit Testing:

1: I'd suggest that you use Nunit. Mstest is in included in Visual Studio but Nunit is more flexible (TestCase, categories) and more people are using it than Mstest, at least the ones I know :-)

2: Web tests are when you use a special tool to navigate your site to see that everything is working. A frequently used tool is Selenium but there are others. I think some versions of Visual Studio have something like this included. These kind of tests are useful since they test everything from the views down to the database as everything has to work together to serve a web page to the browser but they're also much more coarse-grained.

3: I think a good approach is to try to unit test everything you feel there is a need to test (including EPiServer stuff) but if you get stuck then swallow your pride and give up. Some parts will be pretty easy to unit test and some not and it's not worth spending excessive amounts of time on the difficult cases. Personally I think it's okay to initiate standard properties the way I showed above but it took me some time to figure it out and since the tests get messier with these statements I tend to extract them into reusable private helper functions in the test fixture classes. In some other cases I have simply given up...

4: I'm afraid I'm not sure I'm allowed to post any code from my current project, and before that I really haven't worked much with EPiServer in the last years so I can't offer you much in the way of code examples. But in general I would say that it's a good idea to test your view models when they contain logic (calculations, validations and that sort of thing). What I'm saying is that testing may be easier if they don't contain much EPiServer references but since that may be unavoidable you'll have to try and see what works.

Testing controllers and logic that retrieves pages may be more difficult since they assume a state that normally comes from Http context and a datasource and some of this can be mocked but it's not always easy. But again, I think you should try to test everything you feel shoold be tested as long as you're prepared it might not always be obvious how to do it. :-)

Mocking framework:

As Henrik, I've use FakeItEasy in a few projects and I think it's pretty good. Moq is another good alternative and I'm sure there are others out there that are also very competent.

Cheers,

Emil

#115672
Jan 15, 2015 23:25
Vote:
 

Henrik, it's funny that when I worked at Active I regarded myself as a somewhat moderate TDD proponent. It's useful in many cases but not always. But now when I meet people it feels like I'm the TDD guy everywhere I come. :-) I suppose we had some firm believers at the company that affected the point of reference...

Anyway, I'm interested in what you mean by using IContent and IContentRepository. How would you do that if you wanted to test a page method like this:

public string GetFooterText()
{
  return PageName + " published at " + StartPublish;
}
  

The most logical way is something like this

var page = new MyPage();
page.PageName = "a";
page.StartPublish = DateTime(2015,1,2);

Assert.That(page.GetFooterText(), Is.EqualTo("a published at 2015-01-02");

But this does of course not work. How would you write a test for a function like this?

See you around!

Emil

#115673
Jan 15, 2015 23:39
Vote:
 

My 2 cents would be: does really GetFooterText() method is something that needs to be tested? :)

#115674
Jan 16, 2015 0:49
Vote:
 

Yes it is :-)

It's just an example, not real code, but even if it was I would argue that it needs a test. Anyway that's beside the point, the question is how to test code like this...

/Emil

#115681
Jan 16, 2015 8:39
Vote:
 

I would disagree, I think just like Valdis that it is not something that needs to be tested, if so, you have to write test for every part of your and EPiServers code. 

To answer how to do it,

How do the class where MyPage exist in looks like?

If you like to be able to test it you should declare it like this:

public class ClassWithMyPage
{
    private readonly IContentRepository _contentRepository;
    private readonly ContentReference _myPage;

    public ClassWithMyPage(IContentRepository contentRepository, ContentReference myPage)
    {
        _contentRepository = contentRepository;
        _myPage = myPage;
    }

    public PageData MyPage()
    {
        //How do you know what page? I assum you have set the contentref
        return _contentRepository.Get<PageData>(_myPage);
    }
}

Then, All you have to do is to mock your own IContentRepository.

#115685
Jan 16, 2015 9:16
Vote:
 

I don't understand the objection that the code does not have to be tested. Why not? But let's not turn this into an argument of testablility. Although an interesting subject, let's just assume we do indeed want to test this method.

I'm afraid I don't quite understand your code. How would I use it?

This is what MyPage looks like in this contrived example:

public class MyPage : PageData
{
  public string GetFooterText()
  {
    return PageName + " published at " + StartPublish;
  }
}

It doesn't matter what the method does, the problem I have is that it's hard to write good tests for simple code like this. And this is just the most basic example I could think of, it gets much more messy in more complex cases...

#115754
Edited, Jan 18, 2015 12:54
Vote:
 

Hi Emil

Ok, now I understand, MyPage class is a definition for a pagetype/contenttype.

First I would say that GetFooterText() should be named FooterText and converted to a property that looks like this:

public string FooterText
  {
    get
    {
       return string.Format("{0} published at {1}",PageName,StartPublish.ToString("yyyy-MM-dd HH:mm:ss");
    } 
  }

But that does not have anything to do with this, only the way I prefer it.

For the testing, the problem for you is that you are trying to access a property when it is not created or accessible. The reason is that you are creating the page in the "wrong" way.

If you read on this page you can see how to create a page programmatically:

http://world.episerver.com/documentation/Items/Developers-Guide/EPiServer-CMS/75/Content/Creating-a-page-programmatically/

There you will see that if you create a page, you should do it like this:

IContentRepository contentRepository = EPiServer.ServiceLocation.ServiceLocator.Current.GetInstance<IContentRepository>();
StandardPage standardPage = contentRepository.GetDefault<StandardPage>(parent);

In this code you see that I am using GetDefault<T> and that function does a whole lot of things (use JustDecomplile or something else to see what) that you need.

This will be a problem for you because the code also use ServiceLocator to get the IContentRepository so you have to Mock your own ContentRepository and alsop the GetDefault function in it.
After that it is just go for it.

I agree that this looks and is hard work to do, that is why I think better of integration tests and specflow or something like that.

And do go into the Test or no Test discussion, but do it here in the blog post I posted yesterday

http://world.episerver.com/blogs/Henrik-Fransas/Dates/2015/1/doing-unit-testing-or-not-not-doing-unit-testing/

#115756
Jan 18, 2015 20:47
Vote:
 

Thank you for the link about how to create a page programmatically, that was a good read.

It looks like we can agree that a test such as the one I'm after is indeed a little more tricky to write than one would initially expect. And that's my point, the EPiServer architecture does not result in projects that are easily testable. And that is a bad sign, if it's not very testable then that is because of underlying architectural problems. And as the documentation is slightly (hrmph...) lacking you're often left with the reverse engineering option, which you too mention. That's also a bad sign...

Still, there are some great sites out there written with EPiServer and the platform has many good properties. But testability is not one of them.

Funny that I triggered a blog post discussion about this subject, we'll see if I can keep myself away from that :-)

Cheers

Emil

#115757
Jan 18, 2015 21:35
Vote:
 

My next 2 cents would be: that GetFooterText() (regardless either this is method or property) - for me does not seems to be placed in page definition, but rather I would move it over to viewmodel or service model if you are service page data via some service. And try to avoid ServiceLocator. It's an evil guy hidden behind a good name..

#115758
Jan 18, 2015 21:40
Vote:
 

Yes, ServiceLocator gives me the chills too ;-) But it's used all over the EPiServer code base and seem to be the standard DI pattern in EPiServer projects. Which is very strange to me, I'm not seeing this pattern used anywhere else these days.

And you're probably right about that a method/property such as the one in my example would be better suited in a view model or a helper method, or whatnot.

But if you're using a view model you'll want to map the page data properties to the view model properties and that mapping should be testable, and we have the same problem again :-)

#115759
Jan 18, 2015 21:46
Vote:
 

It is fun to discuss.

Valdis, true about ServiceLocator, what would you use instead, add a parameter to the constructor? (or am I missing how it works?)

#115760
Jan 18, 2015 22:05
Vote:
 

I recently started a new development position, where I am primarily responsible for fixes, enhancements and customizations to an EPiServer-based application. This is my first time exposed to EPiServer.

I am a TDD practitioner. However, due to reasons many have mentioned, testing EPiServer can sometimes seem outright impossible.

Some of the developers at my company are talking about commencing the extremely gradual process of depracating ServiceLocator use. We are starting with our custom code of course, but I hope to cross the boundary into EPiServer's proper code base eventually.

My pipe dream is to improve overall testability of the app.

One of the more daunting areas of effort I am anticipating is the wrapping of MediaChase objects. Decompilation will be required.

Have any of you considered taking on this endeavor? Has anyone tried? I would be curious to hear your experiences.

#179848
Jun 23, 2017 0:34
This topic was created over six months ago and has been resolved. If you have a similar question, please create a new topic and refer to this one.
* 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.