Unit testing with Umbraco

3,433 views
Skip to first unread message

Shannon Deminick

unread,
Jul 23, 2013, 8:53:18 PM7/23/13
to umbra...@googlegroups.com
Hey everyone, I was going to reply directly to Jorge but figured that this is worth sharing and discussing!

So here's the details... I got a tweet this week:
How can I test RenderMvcController and SurfaceController in #umbraco? Very difficult to mock the UmbracoContext @Shazwazza @sitereactor

Which i replied:
@jorgelusar @sitereactor #umbraco there's an EnsureContext method, then you can mock the HttpContext

and the reply to that:
@Shazwazza @sitereactor True, but you need to pass the ApplicationContext with is also very difficult to mock and ctr is internal

and was directed to here to see how the progress was going:

And here's what I was going to reply until I realized that this reply would be better suited to our g-groups here:


Hey mate,

Since you are already using Nunit, currently the simplest way to run your unit tests is to use our test project with our existing base classes (which are public). 

The most basic test class to inherit from is:

Umbraco.Tests.TestHelpers.BaseUmbracoApplicationTest

* This will expose a very basic ApplicationContext and ensure things are setup/torn down correctly.
* Then you can of course use it for your test

If you need a db to test against you can use:

Umbraco.Tests.TestHelpers.BaseDatabaseFactoryTest

If you want to test web stuff you can use:

Umbraco.Tests.TestHelpers.BaseRoutingTest

* this will expose the RoutingContext which exposes UmbracoContext, etc...
* Since this inherits from the BaseDatabaseFactoryTest it will create a db for you but if you don't need one then you can override DatabaseTestBehavior and this:

        protected override DatabaseBehavior DatabaseTestBehavior
        {
            get { return DatabaseBehavior.NoDatabasePerFixture; }
        }

Also, some things in the HttpContext for some tests need to work properly including some stuff in controllers, we have a mocked HttpContext that works for everything in our tests. Umbraco.Tests.TestHelpers.FakeHttpContextFactory which exposes: HttpContextBase and a RequestContext. The BaseDatabaseFactoryTest actually exposes a FakeHttpContextFactory  directly but you can also use it in your code.

Now, I realize that people may not want to use these base classes (plus to do so, currently you'd need to compile the source since we're not currently distributing our Test dll but that is pretty straight forward). The problem with exposing any ctor's or factories for the ApplicationContext is that it can be rather complex with the objects it requires such as the DatabaseContext and ServiceContext and if we do expose ways to create it then people will start actually doing so which we don't want to do (apart from in unit tests). So here's what I propose:

* We release the Test project DLL as part of our downloads section
* We expose more even more test helpers to achieve all of this stuff
* For unit tests people can include this DLL to do what they want even if they are not running Nunit
* Inform everyone that the test dll is not to be used on websites or anything else except for unit testing and that they use it at their own risk if not used explicitly for testing!
* Document all of this stuff

There's also one last way in the test project to get an ApplicationContext instance available and that is to look at the Umbraco.Tests.BootManagers.TestApp and the Umbraco.Tests.BootManagers.TestBootManager, the latter exposes a protected ApplicationContext which is also a simple (non db enabled) context.

I'll chat to the guys and see what we can do about adding the test dll to the downloads and make part of our build process.

Looking forward to feedback!
Cheers!
Shan

jorg...@gmail.com

unread,
Jul 24, 2013, 7:00:02 AM7/24/13
to umbra...@googlegroups.com
Hi Shannon,

Thanks for getting back on this. Much appreciated.

I think it would also be a good idea to have a section on the MVC documentation on http://our.umbraco.org/documentation/reference/mvc/ regarding unit test.

Also, it might be a good idea to have a screencast on http://beta.umbraco.tv/videos/developer/fundamentals/ and show how we could test umbraco controllers.

Cheers,

Jorge

Morten Christensen

unread,
Aug 6, 2013, 5:59:21 AM8/6/13
to umbra...@googlegroups.com
I think there are couple of things we can do to improve the testing part of Umbraco for developers by improving the current mishmash of tests in Umbraco.Tests :)

My suggestion would be to split the current test project into 3 projects:
Umbraco.Tests.Base
Umbraco.Tests.UnitTests
Umbraco.Tests.IntegrationTests

The Base project would contain all the helpers, base fixtures, mocks, stubs etc. which can be shared with the community to better enable testing the various bits that people have continued to request.
UnitTests project for everything that is actually unit tests and the IntegrationTests for all the tests that are using a database and/or other types of integrations.
The integration tests is what takes time to run, so if we can separate that out (which btw is normal practice ;)) people might actually want to run and write unit tests before pushing to our repo on github - a bonus dream scenario would be to have all tests compatible with NCrunch/MightyMoose so tests can be run on the fly, so you don't need to stop and wait for ALL tests to run before pushing.
As far as I remember its primarily the dependencies to various settings thats causing problems for NCrunch - and if people in the community also use these types of tools, it would be great if they could continue to do so. Instead of having to disable it for all tests that involve umbraco.

It will of course take a bit of effort to split things and streamline the Base project, so that its easy and intuitive for developers wanting to test various bits involving umbraco, but think it will be worth it - maybe as a freedom friday project. Would also make the Core tests a bit more structured.


- Morten

Shannon Deminick

unread,
Aug 6, 2013, 8:14:38 PM8/6/13
to umbra...@googlegroups.com
Yup, I'd love to help out with an FF project to get this all going. I'd also like to ensure in our Base tests project that we can figure out an easy way to mock the services exposed on the ApplicationContext (and probably many other things). This will require some code juggling and probably changing some of the interfaces on the services... but as we've discussed we aren't going to consider those breaking changes. If we can do this then testing much of this stuff will becomes so much easier.

Jason Prothero

unread,
Aug 29, 2013, 5:49:48 PM8/29/13
to umbra...@googlegroups.com
Shannon,

I included the Umbraco.Tests reference in my unit testing project and inherit from BaseUmbracoApplicationTest.  However, when I run my tests, I get this error (even when using an essentially blank test):

Merchello.Tests.UnitTests.WebControllers.InvoiceControllerTests.GetCustomerByKeyReturnsCorrectItemFromRepository:
SetUp : System.MissingMethodException : Method not found: 'Umbraco.Core.CacheHelper Umbraco.Core.CacheHelper.CreateDisabledCacheHelper()'.

I have a reference to Umbraco.Core.  What am I doing wrong here?



Thanks,
Jason

Jason Prothero

unread,
Aug 29, 2013, 6:31:55 PM8/29/13
to umbra...@googlegroups.com
Oops, I was using the v7 dll.

Thanks,
Jason

jorg...@gmail.com

unread,
Sep 18, 2013, 5:57:52 AM9/18/13
to umbra...@googlegroups.com
To unit test a Umbraco RenderMvcController, you need to [grab the source code from github][1], compile the solution yourself, and get the Umbraco.Tests.dll and reference it on your test project.

In addition to that, you need to reference the SQLCE4Umbraco.dll which is distributed with the Umbraco packages, and Rhino.Mocks.dll which is internally for mocking.

To help you with this, I have compiled put the Umbraco.Tests.dll for Umbraco 6.1.5 and put it together with the Rhino.Mocks.dll and put it on [this zip file][2].

Finally, derive your test from BaseRoutingTest, override the DatabaseTestBehavior to
NoDatabasePerFixture, and get the UmbracoContext and HttpBaseContext by calling the GetRoutingContext method, as in the code below:

using System;
using Moq;
using NUnit.Framework;
using System.Globalization;
using System.Web.Mvc;
using System.Web.Routing;
using Umbraco.Core.Models;
using Umbraco.Tests.TestHelpers;
using Umbraco.Web;
using Umbraco.Web.Models;
using Umbraco.Web.Mvc;

namespace UnitTests.Controllers
{
public class Entry
{
public int Id { get; set; }
public string Url { get; set; }
public string Title { get; set; }
public string Summary { get; set; }
public string Content { get; set; }
public string Author { get; set; }
public string[] Tags { get; set; }
public DateTime Date { get; set; }
}

public interface IBlogService
{
Entry GetBlogEntry(int id);
}

public class BlogEntryController : RenderMvcController
{
private readonly IBlogService _blogService;

public BlogEntryController(IBlogService blogService, UmbracoContext ctx)
: base(ctx)
{
_blogService = blogService;
}

public BlogEntryController(IBlogService blogService)
: this(blogService, UmbracoContext.Current)
{
}

public override ActionResult Index(RenderModel model)
{
var entry = _blogService.GetBlogEntry(model.Content.Id);

// Test will fail if we return CurrentTemplate(model) as is expecting
// the action from ControllerContext.RouteData.Values["action"]
return View("BlogEntry", entry);
}
}

[TestFixture]
public class RenderMvcControllerTests : BaseRoutingTest
{


protected override DatabaseBehavior DatabaseTestBehavior
{
get { return DatabaseBehavior.NoDatabasePerFixture; }
}

[Test]
public void CanGetIndex()
{
const int id = 1234;
var content = new Mock<IPublishedContent>();
content.Setup(c => c.Id).Returns(id);
var model = new RenderModel(content.Object, CultureInfo.InvariantCulture);
var blogService = new Mock<IBlogService>();
var entry = new Entry { Id = id };
blogService.Setup(s => s.GetBlogEntry(id)).Returns(entry);
var controller = GetBlogEntryController(blogService.Object);

var result = (ViewResult)controller.Index(model);

blogService.Verify(s => s.GetBlogEntry(id), Times.Once());
Assert.IsNotNull(result);
Assert.IsAssignableFrom<Entry>(result.Model);
}

private BlogEntryController GetBlogEntryController(IBlogService blogService)
{
var routingContext = GetRoutingContext("/test");
var umbracoContext = routingContext.UmbracoContext;
var contextBase = umbracoContext.HttpContext;
var controller = new BlogEntryController(blogService, umbracoContext);
controller.ControllerContext = new ControllerContext(contextBase, new RouteData(), controller);
controller.Url = new UrlHelper(new RequestContext(contextBase, new RouteData()), new RouteCollection());
return controller;
}
}
}

This code has only been tested in Umbraco 6.1.5.

[1]: https://github.com/umbraco/Umbraco-CMS
[2]: http://jlusar.es/get/umbraco%20mvc%20test/UmbracoTest.zip

Jason Prothero

unread,
Sep 18, 2013, 12:56:45 PM9/18/13
to umbra...@googlegroups.com, jorg...@gmail.com
Thanks Jorge,

I did figure this out just before you posted this, but this is great for the next poor soul.  There is one more thing that I had to do to get it working.  The TestHelpers.cs copies in the umbraco.config from Umbraco.Web.UI/config/ so I had to create that directory structure or else the UmbracoContext didn't get created properly.

I'm not sure that is a great dependency to have for testing, perhaps it could simply create a version from code if that file doesn't exist?  

Anyways, it works!  


Thanks,
Jason

Jason Prothero

unread,
Sep 18, 2013, 1:38:40 PM9/18/13
to umbra...@googlegroups.com, jorg...@gmail.com
Sorry, 

The file was umbracoSettings.Release.config



Thanks,
Jason

Shannon Deminick

unread,
Sep 25, 2013, 7:49:42 PM9/25/13
to umbra...@googlegroups.com, jorg...@gmail.com
Just an FYI The latest codebase has removed rhinomocks which has been completely replaced by Moq.
And we will soon (i hope) get around to cleaning up this test project and exposing all the goodies that people need to nicely test - just so much work to do atm :)

Aaron Powell

unread,
Oct 1, 2013, 8:10:54 PM10/1/13
to umbra...@googlegroups.com

Ok so I’m going to add my 2c here because it’s been a while since I voiced my opinions on this mailing list and everyone loves my opinions :P.

 

Simply put I don’t want to use your test base classes. I see that as a fundamental barrier to me being able to do any unit testing against SurfaceControllers or any of the other Umbraco-specific controller types.

The reason I don’t want to use them is I don’t trust them in unit test scope. When writing a unit test I don’t want to rely on something I don’t have any control over, setting up dependencies that I have no visibility on, immediately my tests are no longer testing my code but instead running code that might impact the outcome without my knowledge.

 

Looking at the dependency chain described in the other posts here it suggests inheriting from BaseRoutingTest which in turn inherits from BaseWebTest which inherits BaseDatabaseFactoryTest and that finally inherits BaseUmbracoApplicationTest.

Digging through these classes there are a number of things that are immediately going to have me noping out immediately such as:

·         Generating a folder structure

·         Loading plugins into the app domain

·         Creating a database (!!)

 

Simply put even before my test starts up hundreds of lines of code have been executed that I don’t have any control over and that I didn’t request.

 

For the record the controller action I wanted to test was communicating to an external service in our system and then rendering a view. In that view we then use come Umbraco helpers to insert content and a child macro, hence the reason I’m down the SurfaceController path.

 

My solution

Now that I’ve voiced my opinion on why the current solution doesn’t work it seems only fair to add my own solution, that would be to expose an interface for the UmbracoContext, like IUmbracoContext. This could be returned via the UmbracoContext.Current member so that the current codebase doesn’t really need to change, really nothing would be that different, it just means that instead of using a class (which is virtually impossible to stub out due to internal constructors) you have an interface instead which you can work with, stub out and control.

 

If/When you write integration tests then the Umbraco test classes would make sense but when you’re doing a unit test on controller actions that don’t need Umbraco features you should be able to work without them.

 

Aaron Powell
IE MVP | IE userAgent


http://www.aaron-powell.com | http://twitter.com/slace | Skype: aaron.l.powell | Github | BitBucket

--
You received this message because you are subscribed to the Google Groups "Umbraco development" group.
To unsubscribe from this group and stop receiving emails from it, send an email to umbraco-dev...@googlegroups.com.
To post to this group, send email to umbra...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/umbraco-dev/4f7a5eb0-16b7-4668-92f5-37463c679fd5%40googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Shannon Deminick

unread,
Oct 1, 2013, 8:26:50 PM10/1/13
to umbra...@googlegroups.com
Yes Aaron, we love your opinions :P ... but we also already know that this all needs work (as already mentioned).

UmbracoContext is a complicated one and is so for a few reason and much as to do with maintaining all sorts of compatibility with legacy code, etc... Creating an interface of it won't solve all of the problems either as we'd have to make interfaces for all of it's dependencies = a lot of work and most likely breaking changes and there are a lot of internal parts in there that need to exist for some things to work and we cannot expose them publicly.. Macro rendering are 100% still old legacy code and you probably wont be able to unit test them anyways. In the core we have to battle with all this legacy code but are doing our best to slowly phase it out and slowly bring in nicer objects.

As for using the test base classes - this is a recommended interim solution until we release a nice testing lib but currently i don't have time. You also don't need to have them create a database you can override that as well as many other things so I'm sure you can figure something out.

Instead of creating an interface - we plan on having some simple test helpers to just give you these individual objects to do with as you like. It's not a perfect solution but we have to deal with a codebase where some parts are over 8 yrs old!

Aaron Powell

unread,
Oct 1, 2013, 9:02:53 PM10/1/13
to umbra...@googlegroups.com

The point about macro rendering was to illustrate why I was using a SurfaceController on something that really didn’t need any Umbraco in the controller, in fact my controller’s actions only Umbraco features are RedirectToUmbracoPage on the POST action.

 

Now I still think an interface is a viable idea, if we look at the public instance members of UmbracoContext (in 6.1.5) there are:

·         Application

·         Security

·         ContentCache

·         MediaCache

·         IsFrontEndUmbracoRequest

·         PublishedContentRequest

·         HttpContext

·         IsDebug

·         PageId

·         UmbracoUser

·         InPreviewMode

 

Now if we discount what isn’t an Umbraco type we’re left with:

·         Application

·         Security

·         ContentCache

·         MediaCache

·         PublishedContentRequest

·         UmbracoUser (although I discount this because I’m pretty sure you could mock that)

 

And well yes the dependency chain expands out from there. But if at the very least UmbracoContext was an interface that you could assign null to all those you’re making Controllers infinitely more testable, it’s not until you’re wanting to consume Umbraco from a controller action (which to me is a design smell to begin with, you should do that in the View) that you start hitting roadblocks.

 

To illustrate my point, you can’t unit test this controller:

public class MySurfaceController : SurfaceController {

    [ChildActionOnly]

    public ActionResult Index() {

        return View(new MyModel());

    }

}

 

Like this:

[TestClass]

public class MySurfaceControllerTests {

    [TestMethod]

    public void IndexReturnsViewResult() {

        var controller = new MySurfaceController();

 

        var result = controller.Index() as ViewResult;

 

        Assert.IsNotNull(result);

    }

}

 

Without taking dependencies on the file system, a database and executing several hundred lines of code. If I could pass null or a stub of UmbracoContext (or IUmbracoContext) then my test can pass.

 

Yes I’m aware this is a slightly contrived example, but it’s not far off what I was actually wanting to do.

 

Aaron Powell
IE MVP | IE userAgent


http://www.aaron-powell.com | http://twitter.com/slace | Skype: aaron.l.powell | Github | BitBucket

 

From: umbra...@googlegroups.com [mailto:umbra...@googlegroups.com] On Behalf Of Shannon Deminick
Sent: Wednesday, 2 October 2013 10:27 AM
To: umbra...@googlegroups.com
Subject: Re: Unit testing with Umbraco

 

Yes Aaron, we love your opinions :P ... but we also already know that this all needs work (as already mentioned).

--

You received this message because you are subscribed to the Google Groups "Umbraco development" group.
To unsubscribe from this group and stop receiving emails from it, send an email to umbraco-dev...@googlegroups.com.
To post to this group, send email to umbra...@googlegroups.com.

Shannon Deminick

unread,
Oct 1, 2013, 9:16:24 PM10/1/13
to umbra...@googlegroups.com
There's was a discussion someplace else about this and passing in null - i thought it was this thread but apparently not. Of course the absolute easiest way to let you do what you want currently is to remove the null check for UmbracoContext - you can submit a PR if you want for that but that's not going to solve your issue right now unless you want to work on a custom build and in that case you can just change what you like in your project anyways.

In the interim, you can either use the base classes - if you don't want the integration stuff to initialize no problem just hack a little bit:

* Create a new class deriving from BaseDatabaseFactoryTest
* Expose a public method on there like GetUmbracoContext
* return the result of the BaseDatabaseFactoryTest.GetUmbracoContext

There you go.

We will get the unit test project to a place that is nicely usable at some stage but time is pretty tight atm.

Aaron Powell

unread,
Oct 2, 2013, 12:53:27 AM10/2/13
to umbra...@googlegroups.com

 

Aaron Powell
IE MVP | IE userAgent


http://www.aaron-powell.com | http://twitter.com/slace | Skype: aaron.l.powell | Github | BitBucket

 

From: umbra...@googlegroups.com [mailto:umbra...@googlegroups.com] On Behalf Of Shannon Deminick
Sent: Wednesday, 2 October 2013 11:16 AM
To: umbra...@googlegroups.com
Subject: Re: Unit testing with Umbraco

 

There's was a discussion someplace else about this and passing in null - i thought it was this thread but apparently not. Of course the absolute easiest way to let you do what you want currently is to remove the null check for UmbracoContext - you can submit a PR if you want for that but that's not going to solve your issue right now unless you want to work on a custom build and in that case you can just change what you like in your project anyways.

--

You received this message because you are subscribed to the Google Groups "Umbraco development" group.
To unsubscribe from this group and stop receiving emails from it, send an email to umbraco-dev...@googlegroups.com.
To post to this group, send email to umbra...@googlegroups.com.

Shannon Deminick

unread,
Oct 2, 2013, 1:16:30 AM10/2/13
to umbra...@googlegroups.com
Time is extremely tight right now, as much as I'd love to jump in there and get something nicer working for you. One fundamental problem with changing to an interface is that the UmbracoContext exposes lots of internal bits, so inevitably in the core we'll be casting this to the real instance to get at those internal parts which also means that if ( "when " - because people will) start trying to use an interface that isn't the object we're looking for in their unit tests when they try to do stuff, they will get errors and will wonder why.

The next problem is all of the public things UmbracoContext exposes, we would inevitably have to make interfaces of a few of those too like ApplicationContext, etc... this is all a ton of work and yet we still don't want to expose all of the underlying stuff in there publicly so we're back at this option -> For us to just give you a simple test helper class to create these instances for you to use - this is an easier and better option for us.

So in the interim until we have time to sort this out you can use the base classes - if you don't want to inherit than you can use the hack I've mentioned above.

Lars-Erik Aabech

unread,
Oct 8, 2013, 3:24:25 PM10/8/13
to umbra...@googlegroups.com

I just have to toss in a couple of cents for when times aren’t as tight.

 

Context is that I can’t not unit-test this controller/model stuff I have any longer, it’s gotten too complex.

Guess what? I’m in a bee-hive of internal/mocking hell. (Yes, I tried the hacks and base classes, but they don’t do)

Anyway, I’m gonna get through it no matter, and I don’t really care whether you’re gonna break it in the future. I’ll adapt.

 

But I really really have to comment on one thing you said below:

> “One fundamental problem with changing to an interface is that the UmbracoContext exposes lots of internal bits, so inevitably in the core we'll be casting this to the real instance to get at those internal parts

The whole point of using interfaces and layering your software into modules and interchangeable parts is that you DON’T want to expose and / or cast stuff to conrete types.

Get over it! You’re doing it wrong!

Sorry for the harsh message, but fact of the matter is that that is the only way to build maintainable software, and the only way we can trust the API and our own software built on top of it.

 

Haven’t looked at U7 much yet, but I really hope you’ve either read or will read:

-          “Working effectively with legacy code” http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052

-          “Clean code” http://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882/ref=sr_1_1?s=books&ie=UTF8&qid=1381259819&sr=1-1&keywords=clean+code

-          and “Aglie software development” http://www.amazon.com/Software-Development-Principles-Patterns-Practices/dp/0135974445/ref=pd_sim_b_5

before adding ANY new class with either internal or private declaration and/or ctor; and which doesn’t inherit from a public interface, and is ONLY used as such.

I’d much rather change my code when you break backwards compatibility.

 

OK, point made, frustration vented and tips provided.

Sorry again for being a bit frustrated just now.

 

Looking forward to better APIs. J

 

Lars-Erik

 

From: umbra...@googlegroups.com [mailto:umbra...@googlegroups.com] On Behalf Of Shannon Deminick
Sent: 2. oktober 2013 07:17
To: umbra...@googlegroups.com
Subject: Re: Unit testing with Umbraco

 

Time is extremely tight right now, as much as I'd love to jump in there and get something nicer working for you. One fundamental problem with changing to an interface is that the UmbracoContext exposes lots of internal bits, so inevitably in the core we'll be casting this to the real instance to get at those internal parts which also means that if ( "when " - because people will) start trying to use an interface that isn't the object we're looking for in their unit tests when they try to do stuff, they will get errors and will wonder why.

 

The next problem is all of the public things UmbracoContext exposes, we would inevitably have to make interfaces of a few of those too like ApplicationContext, etc... this is all a ton of work and yet we still don't want to expose all of the underlying stuff in there publicly so we're back at this option -> For us to just give you a simple test helper class to create these instances for you to use - this is an easier and better option for us.

 

So in the interim until we have time to sort this out you can use the base classes - if you don't want to inherit than you can use the hack I've mentioned above.

--

You received this message because you are subscribed to the Google Groups "Umbraco development" group.
To unsubscribe from this group and stop receiving emails from it, send an email to umbraco-dev...@googlegroups.com.
To post to this group, send email to
umbra...@googlegroups.com.

Lars-Erik Aabech

unread,
Oct 8, 2013, 4:53:53 PM10/8/13
to umbra...@googlegroups.com

Say no more...

The entire code below had to be written to achieve the three lines in bold/red near the top of the code.

(You can clearly see where I started to get tired of “clean code” J )

The extension method under test is actually rather new, but I hope this code serves to show that there is still something horribly wrong in some of the architecture going into the system.

 

using System;

using System.Collections.Generic;

using System.Reflection;

using NUnit.Framework;

using Rhino.Mocks;

using Umbraco.Core;

using Umbraco.Core.Models;

using Umbraco.Core.Persistence;

using Umbraco.Core.Persistence.Querying;

using Umbraco.Core.Persistence.Repositories;

using Umbraco.Core.Persistence.SqlSyntax;

using Umbraco.Core.Persistence.UnitOfWork;

using Umbraco.Core.PropertyEditors;

using Umbraco.Core.Publishing;

using Umbraco.Core.Services;

using Umbraco.Web;

 

namespace Umbraco.Web.Tests

{

       [TestFixture]

       public class ApplicationContextMockingTests

       {

             private static IDatabaseUnitOfWorkProvider databaseUnitOfWorkProvider;

             private static RepositoryFactory repositoryFactory;

             private static IContentService contentService;

             private static IMediaService mediaService;

             private static IUnitOfWorkProvider unitOfWorkProvider;

             private static BasePublishingStrategy basePublishingStrategy;

             private const BindingFlags PrivateInstance = BindingFlags.Instance | BindingFlags.NonPublic;

             private const BindingFlags PublicStatic = BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy;

 

             [SetUp]

             public void SetUp()

             {

                    databaseUnitOfWorkProvider = MockRepository.GenerateStub<IDatabaseUnitOfWorkProvider>();

                    repositoryFactory = MockRepository.GenerateStub<RepositoryFactory>();

                    unitOfWorkProvider = MockRepository.GenerateStub<IUnitOfWorkProvider>();

                    basePublishingStrategy = MockRepository.GenerateStub<BasePublishingStrategy>();

                    contentService = MockRepository.GenerateStub<IContentService>();

                    mediaService = MockRepository.GenerateStub<IMediaService>();

             }

 

             [Test]

             public void WowIReallyHaveToDoAllThisToUseSimpleTypeConvertersWithAMockedInterfacesExtensions()

             {

                    CanCreateFullPackage();

 

                    var appBase = MockRepository.GenerateStub<UmbracoApplicationBase>();

                    var bootMgr = new PleaseJustAPartialBootManager(appBase);

                    bootMgr.InitializeResolvers();

                    bootMgr.FreezeResolution();

 

                    SqlSyntaxContext.SqlSyntaxProvider = new SqlServerSyntaxProvider();

 

                    var uow = MockRepository.GenerateStub<IDatabaseUnitOfWork>();

                    unitOfWorkProvider.Stub(p => p.GetUnitOfWork()).Return(uow);

                    var repo = MockRepository.GenerateStub<IContentTypeRepository>();

                    repositoryFactory.Stub(f => f.CreateContentTypeRepository(Arg<IDatabaseUnitOfWork>.Is.Anything)).Return(repo);

 

                    var contentType = new ContentType(-1);

                    contentType.AddPropertyGroup("");

                    contentType.AddPropertyType(

                           new PropertyType(

                                  new DataTypeDefinition(-1, new Guid("9910AA59-FF84-4F37-A61C-51D295499C83"))

                                  )

                           {

                                  Alias = "a"

                           }

                    );

                    repo.Stub(r => r.GetByQuery(Arg<IQuery<IContentType>>.Is.Anything))

                           .Return(new[] { contentType });

 

                    var content = MockRepository.GenerateStub<IPublishedContent>();

                    content.Stub(c => c.DocumentTypeAlias).Return("ohJustADocType");

                    var prop = MockRepository.GenerateStub<IPublishedContentProperty>();

                    prop.Stub(p => p.Value).Return("a");

                    content.Stub(c => c.GetProperty("a")).Return(prop);

 

                  var value = content.GetPropertyValue<string[]>("a");

 

                  Assert.IsNotNull(value);

                  Assert.AreEqual(1, value.Length);

             }

 

             [Test]

             public void CanCreateApplicationContext()

             {

                    var context = CreateApplicationContext();

                    Assert.IsNotNull(context);

             }

 

             [Test]

             public void CanCreateServiceContext()

             {

                    var context = CreateServiceContext();

                    Assert.IsNotNull(context);

             }

 

             [Test]

             public void CanCreateContentTypeService()

             {

                    var service = CreateContentTypeService();

                    Assert.IsNotNull(service);

             }

 

             [Test]

             public void CanSetContentTypeServiceOnServiceContext()

             {

                    var context = CreateServiceContext();

                    var contentTypeService = CreateContentTypeService();

                    SetService(context, "_contentTypeService", contentTypeService);

                    Assert.AreSame(contentTypeService, context.ContentTypeService);

             }

 

             [Test]

             public void CanSetServiceContextOnApplicationService()

             {

                    var applicationContext = CreateApplicationContext();

                    var serviceContext = CreateServiceContext();

                    SetFieldValue(applicationContext, "_services", serviceContext);

 

                    Assert.AreSame(serviceContext, applicationContext.Services);

             }

 

             [Test]

             public void CanCreateFullPackage()

             {

                    var applicationContext = CreateApplicationContext();

                    var serviceContext = CreateServiceContext();

                    var contentTypeService = CreateContentTypeService();

                    var dataTypeService = CreateDataTypeService();

                    SetService(serviceContext, "_contentTypeService", contentTypeService);

                    SetService(serviceContext, "_dataTypeService", dataTypeService);

                    SetFieldValue(applicationContext, "_services", serviceContext);

                    SetStaticProperty<ApplicationContext>("Current", applicationContext);

 

                    Assert.AreSame(applicationContext, ApplicationContext.Current);

             }

 

             [Test]

             public void CanCreateMappingResolver()

             {

                    var type = Type.GetType("Umbraco.Core.Persistence.Mappers.MappingResolver, Umbraco.Core");

                    if (type == null)

                           throw new Exception("Couldn't create MappingResolver type");

                    var ctor = type.GetConstructor(new[] {typeof (Func<IEnumerable<Type>>)});

                    if (ctor == null)

                           throw new Exception("Didn't find MappingResolver ctor");

                    var resolver = ctor.Invoke(new object[] { new Func<IEnumerable<Type>>(() => Type.EmptyTypes) });

                    SetStaticProperty(type, "Current", resolver);

                    Assert.IsNotNull(resolver);

                    Assert.AreSame(resolver, type.GetProperty("Current", PublicStatic).GetValue(null));

             }

 

             private void SetStaticProperty<T>(string propertyName, object value)

             {

                    var type = typeof (T);

                    SetStaticProperty(type, propertyName, value);

             }

 

             private static void SetStaticProperty(Type type, string propertyName, object value)

             {

                    var prop = type.GetProperty(propertyName, PublicStatic);

                    if (prop == null)

                           throw new Exception("Didn't find property " + propertyName);

                    prop.SetValue(null, value);

             }

 

             private static ContentTypeService CreateContentTypeService()

             {

                    var service = new ContentTypeService(databaseUnitOfWorkProvider, repositoryFactory, contentService, mediaService);

                    return service;

             }

 

             private static DataTypeService CreateDataTypeService()

             {

                    var service = new DataTypeService(databaseUnitOfWorkProvider, repositoryFactory);

                    return service;

             }

 

             private static ApplicationContext CreateApplicationContext()

             {

                    var type = typeof (ApplicationContext);

                    var ctor = type.GetConstructor(PrivateInstance, null, new[] {typeof (bool)}, null);

                    if (ctor == null)

                           throw new Exception("Couldn't find ctor with bool parameter");

                    var context = (ApplicationContext) ctor.Invoke(new object[] {false});

                    return context;

             }

 

             private static ServiceContext CreateServiceContext()

             {

                    var type = typeof (ServiceContext);

                    var ctor = type.GetConstructor(PrivateInstance, null,

                           new[] {typeof (IDatabaseUnitOfWorkProvider), typeof (IUnitOfWorkProvider), typeof (BasePublishingStrategy)}, null);

                    if (ctor == null)

                           throw new Exception("Couldn't find ctor with misc parameters");

                    var context = (ServiceContext) ctor.Invoke(new object[] {databaseUnitOfWorkProvider, unitOfWorkProvider, basePublishingStrategy});

                    return context;

             }

 

             private static void SetService<T>(ServiceContext serviceContext, string fieldName, T contentTypeService)

             {

                    var value = new Lazy<T>(() => contentTypeService);

                    SetFieldValue(serviceContext, fieldName, value);

             }

 

             private static void SetFieldValue(object owner, string fieldName, object value)

             {

                    var field = owner.GetType().GetField(fieldName, PrivateInstance);

                    if (field == null)

                           throw new Exception("Didn't find field " + fieldName);

                    field.SetValue(owner, value);

             }

       }

 

       public class PleaseJustAPartialBootManager : CoreBootManager

       {

             public PleaseJustAPartialBootManager(UmbracoApplicationBase umbracoApplication) : base(umbracoApplication)

             {

             }

 

             public new void InitializeResolvers()

             {

                    base.InitializeResolvers();

             }

 

             public new void FreezeResolution()

             {

                    base.FreezeResolution();

             }

       }

 

       public class StringArrayConverter : IPropertyEditorValueConverter

       {

             public bool IsConverterFor(Guid propertyEditorId, string docTypeAlias, string propertyTypeAlias)

             {

                    return propertyEditorId == new Guid("9910AA59-FF84-4F37-A61C-51D295499C83");

             }

 

             public Attempt<object> ConvertPropertyValue(object value)

             {

                    var str = value as string;

                    var array = new string[0];

                    if (str != null)

                           array = str.Split(new[] {";"}, StringSplitOptions.RemoveEmptyEntries);

                    return new Attempt<object>(true, array);

             }

       }

}

Lars-Erik Aabech

unread,
Oct 8, 2013, 5:10:03 PM10/8/13
to umbra...@googlegroups.com

Just to sum up,

The previously posted code can be shrunk to 10% or so with the following changes to the newly implemented(?) core stuff:

 

-          Make a public empty ApplicationContext ctor

-          Make ApplicationContext.Current settable

-          Make the ServiceContext setter public

-          Change the relevant Lazy<T> in ServiceContext to use the service interfaces and add a setting method

-          Or drop the lazy usage of Lazy and make them regular references by using the age old lazy pattern, and make the properties settable

-          Oh, and while I’m at it – make it a whole lot easier to stub usage of IUnitOfWork and IRepository<T>.
The tight coupling with Query<T>.Builder and MappingResolver f***s everything up.

 

I’m 100% sure nobody’s going to f*** with those members outside a test fixture.

If they do, they’ll delete the code and don’t do it again.

 

Keep up the good work! J

G’night.

 

L-E

Shannon Deminick

unread,
Oct 8, 2013, 8:52:09 PM10/8/13
to umbra...@googlegroups.com
I understand the frustration and I am by no way saying you guys are wrong or anything, i would love to make this a top priority right this minute but i just cannot do that currently. I promise that we will clean all this up soon, i'll see if i can make any sort of headway over the next few weeks as we get v7 closer to release. 
There's already a task regarding the public interfaces at the service level that need to be updated with the correct public implementation - http://issues.umbraco.org/issue/U4-2956
I hate the casting as much as you - all due to ensuring no breaking changes when these were originally created and some oversights regarding the underlying logic that had to be changed.
We've conveniently made it a policy that changes to the service level interfaces we will not consider breaking changes - since only your unit tests should ever break (http://our.umbraco.org/documentation/Development-Guidelines/breaking-changes#Nonbreaking).

We'll make all of this cleaner, better, easier, etc... soon - and are also very accepting of any help in doing so.

Shannon Deminick

unread,
Oct 9, 2013, 1:22:42 AM10/9/13
to umbra...@googlegroups.com
I managed to make some progress on this today in both the 6.2. and 7.0 branches btw.
The end result of the few of today's commits can be seen in Umbraco.Tests.MockTests:
Of course there's still plenty more work to do but it's a start.


Lars-Erik Aabech

unread,
Oct 9, 2013, 4:38:56 AM10/9/13
to umbra...@googlegroups.com

Glad to hear you’re on top of it. J

I’d be happy to contribute, but I guess architectural changes is a bit cumbersome through PRs.

 

Tagging tasks on issues.umbraco.org as “up for grabs” for the community would be nice, not sure if anything like that is already done?

 

L-E

 

From: umbra...@googlegroups.com [mailto:umbra...@googlegroups.com] On Behalf Of Shannon Deminick
Sent: 9. oktober 2013 07:23
To: umbra...@googlegroups.com
Subject: Re: Unit testing with Umbraco

 

I managed to make some progress on this today in both the 6.2. and 7.0 branches btw.

--

You received this message because you are subscribed to the Google Groups "Umbraco development" group.
To unsubscribe from this group and stop receiving emails from it, send an email to umbraco-dev...@googlegroups.com.
To post to this group, send email to
umbra...@googlegroups.com.

Lars-Erik Aabech

unread,
Oct 18, 2013, 10:49:08 AM10/18/13
to umbra...@googlegroups.com

On a sidenote, I took my own advice and adapted, so now I’ve adapted (as in the pattern) my way away from the entire core. J

(Of course using my code generator project with a couple of custom generator implementations – worth checking out todays commits on https://github.com/lars-erik/Umbraco.CodeGen. ;) )

So no rush after all.. keep doing good stuff to 6.2/7.

 

L-E

--

You received this message because you are subscribed to the Google Groups "Umbraco development" group.

To unsubscribe from this group and stop receiving emails from it, send an email to umbraco-dev...@googlegroups.com.
To post to this group, send email to
umbra...@googlegroups.com.

guido....@indivirtual.com

unread,
Oct 22, 2013, 5:30:45 AM10/22/13
to umbra...@googlegroups.com
I have been trying to create a unit test by extending BaseUmbracoApplicationTest. I always end up getting this exception inside Initialize >> SetUp : System.InvalidOperationException : Sequence contains no elements
at System.Linq.Enumerable.First(IEnumerable`1 source)
at Umbraco.Tests.TestHelpers.TestHelper.EnsureUmbracoSettingsConfig()
at Umbraco.Tests.TestHelpers.BaseUmbracoApplicationTest.Initialize()
at UmbracoUnitTestTest.UnitTest1.Initialize() in UnitTest1.cs: line 14

namespace UmbracoUnitTestTest
{
using NUnit.Framework;

using Umbraco.Tests.TestHelpers;

[TestFixture]
public class UnitTest1 : BaseUmbracoApplicationTest
{

[SetUp]
public override void Initialize()
{
base.Initialize();
}

[Test]
public void TestMethod1()
{
Umbraco.Core.ApplicationContext applicationContext = this.ApplicationContext;
Assert.IsNotNull(applicationContext);
}
}
}


Any idea's?

Shannon Deminick

unread,
Feb 4, 2015, 2:37:39 AM2/4/15
to umbra...@googlegroups.com, guido....@indivirtual.com
Hi all i know this is real old but there is some progress being made in the core regarding being able to mock things easier and allow things to be better tested with public APIs. It's slow going but that is mostly due to getting rid of much of the legacy code. Along with some of this progress, I've also created a new PR here: 


which makes it a bit easier to test controllers if you are using the UmbracoHelper to access the data, or you could make things dependent on the individual parts (recommended) so that mocking is even easier. It also makes things a bit easier to mock other non-interfaces, etc... This should get things moving a bit further and there's some examples in the test project. If you've got feedback, please post on the PR. 

Steve Temple

unread,
Feb 12, 2015, 8:26:14 AM2/12/15
to umbra...@googlegroups.com, guido....@indivirtual.com
This looks to me like a big step in the right direction. We're in the process of creating a fully unit tested U7.2.1 site and having some fun with SurfaceControllers which this looks like it will help with.


To get a SurfaceController in which the CurrentPage property will work requires something like this:

private BlogPostSurfaceController GetController()
{
   
// Create contexts via test base class methods

   
var routingContext = GetRoutingContext("/test");
   
var umbracoContext = routingContext.UmbracoContext;
   
var contextBase = umbracoContext.HttpContext;

 
   
// We need to add a value to the controller's RouteData, otherwise calls to CurrentPage
   
// (or RedirectToCurrentUmbracoPage) will fail
 
   
// Unfortunately some types and constructors necessary to do this are marked as internal
 
   
// Create instance of RouteDefinition class using reflection
   
// - note: have to use LoadFrom not LoadFile here to type can be cast (http://stackoverflow.com/questions/3032549/c-on-casting-to-the-same-class-that-came-from-another-assembly
   
var assembly = Assembly.LoadFrom(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "umbraco.dll"));
   
var reflectedRouteDefinitionType = assembly.GetType("Umbraco.Web.Mvc.RouteDefinition");
   
var routeDefinition = Activator.CreateInstance(reflectedRouteDefinitionType);
 
   
// Similarly create instance of PublishedContentRequest
   
// - note: have to do this a little differently as in this case the class is public but the constructor is internal
   
var reflectedPublishedContentRequestType = assembly.GetType("Umbraco.Web.Routing.PublishedContentRequest");
   
var flags = BindingFlags.NonPublic | BindingFlags.Instance;
   
var culture = CultureInfo.InvariantCulture;
   
var publishedContentRequest = Activator.CreateInstance(reflectedPublishedContentRequestType, flags, null, new object[] { new Uri("/test", UriKind.Relative), routingContext }, culture);
 
   
// Set properties on reflected types (not all of them, just the ones that are needed for the test to run)
   
var publishedContentRequestPublishedContentProperty = reflectedPublishedContentRequestType.GetProperty("PublishedContent");
    publishedContentRequestPublishedContentProperty
.SetValue(publishedContentRequest, MockIPublishedContent(), null);
   
var publishedContentRequestProperty = reflectedRouteDefinitionType.GetProperty("PublishedContentRequest");
    publishedContentRequestProperty
.SetValue(routeDefinition, publishedContentRequest, null);
 
   
// Then add it to the route data tht will be passed to the controller context
   
// - without it SurfaceController.CurrentPage will throw an exception of: "Cannot find the Umbraco route definition in the route values, the request must be made in the context of an Umbraco request"
   
var routeData = new RouteData();
    routeData
.DataTokens.Add("umbraco-route-def", routeDefinition);
 
   
// Create the controller with the appropriate contexts
   
var controller = new BlogPostSurfaceController(umbracoContext);
    controller
.ControllerContext = new ControllerContext(contextBase, routeData, controller);

    controller
.Url = new UrlHelper(new RequestContext(contextBase, new RouteData()), new RouteCollection());
   
return controller;
}
 

Now that PublishedContentRequest's constructor is public 1/2 that code isn't needed but there it still requires reflection to get a RouteDefininition that you can put into the route data "Umbraco-route-def" key

With the SurfaceController's CurrentPage property being made virtual that gives more flexibility to potentially override what it does, but adding code to production controllers purely for unit testing is far from ideal.

With this change would there be a way of establishing that route without reflection etc.I can't see it but I'm not overly familiar with that part of the codebase.

Cheers,

Steve

Shannon Deminick

unread,
Feb 12, 2015, 5:21:39 PM2/12/15
to umbra...@googlegroups.com, guido....@indivirtual.com
Thanks for the feedback Steve,

I'll find some time in the next week or so to update the PR with better accessors to that kind of stuff. This will all be a part of 7.3 just fyi. I'll get back to you when I've updated it.

Cheers,
Shannon

Shannon Deminick

unread,
Feb 18, 2015, 11:30:12 AM2/18/15
to umbra...@googlegroups.com, guido....@indivirtual.com
Hi Steve,

This PR has now been merged in to 7.3. The other good news is that PublishedContentRequest ctor has been public for a few versions (IIRC) and now in 7.3 the RouteDefinition class is public (which I'll backport to 7.2.2 as well since that should make things a bit easier).

That should solve your 'CurrentPage' issue i think.

Shannon Deminick

unread,
Feb 18, 2015, 12:06:26 PM2/18/15
to umbra...@googlegroups.com, guido....@indivirtual.com

Steve Temple

unread,
Feb 18, 2015, 12:12:34 PM2/18/15
to umbra...@googlegroups.com, guido....@indivirtual.com
Awesome, #h5yr 

I'll take a look later this week, should clean up the SurfaceController tests

Steve
Reply all
Reply to author
Forward
0 new messages