Back in 6.1, the RenderMvcController inherited directly from System.Web.Mvc.Controller and didn't require any constructor parameters. In 7, RenderMvcController now inherits from UmbracoController, which now requires an umbracoContext parameter to be present.
I assume that this parameter is injected by some Autofac magic at some point in the normal lifecycle, but as the context seems completely unmockable it seems that it's no longer possible to unit test controllers.
This can be resolved by...
a) Allow the umbracoContext parameter to be null.
b) Remove the umbracoContext parameter and, instead, pass it via Properties instead (this is effectively where the context was at before in 6.1)
c) Replace UmbracoContext with IUmbracoContext to allow it to me mockable.
Solution c, seems to be the best option that I can envisage at the moment as that would also allow testing of context sensitive operations in the controller itself.
Here's a patch I worked up of how this could work as a mockable interface. The patch is very simplistic, only covering the interface itself, making several Internal elements of UmbracoContext public (so the interface will expose them), and applying it to several dependent classes.
The larger scope of this is to replace all exposed instances of UmbracoContext across the entire codebase with IUmbracoContext (as there should be no real change at all to the outgoing behaviour it kind of makes sense).
The only annoying issue here is namespacing - Moving to IUmbracoContext means that a lot of the existing internal code that calls the statics, such as UmbracoContext.Current no longer compiles. This is an interesting side effect and indicates that the calls are using the type of the non static UmbracoContext property on the affected classes in order to be able to resolve UmbracoContext.Current rather than resolving it statically.
In order to resolve this issue, I've added several lines of...
using UWeb = Umbraco.Web, so that I can call UWeb.UmbracoContext.Current instead.
The affected classes also have an Umbraco property which blocks direct access to the Umbraco namespace in exactly the same way so long hand typing the namespace was not an option here either.
If I have time, I'm more than happy to consider taking this work on myself and contributing it if someone can point me in the right direction to do so and how widely scoped the change should really apply to (It's not worth applying it across untestable statics for example).
Extension methods and internal constructors in parts of the codebase has made mocking and testing practically impossible in some cases.
For example, since upgrading to U7 I now find that every time .HasProperty() is called on an item of content it calls through to the ContentType object like this...
/// <summary>
/// Gets a value indicating whether the content has a property identified by its alias.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="alias">The property alias.</param>
/// <returns>A value indicating whether the content has the property identified by the alias.</returns>
/// <remarks>The content may have a property, and that property may not have a value.</remarks>
public static bool HasProperty(this IPublishedContent content, string alias)
{
return content.ContentType.GetPropertyType(alias)
!= null;
}
This is not testable architecture. Since IPublishedContent is something you could expect to need mocking on a fairly regular basis, content should inherit from a base class which implements the methods currently registered as extension methods and the methods should themselves be included in the IPublishedContent interface declaration if at all possible.
This approach would allow all methods to be accurately mocked and prevent unexpected and complex test failures.
This applies at a larger level to all of the Extensions statics that are set up on the Interface infrastructure, attaching extension methods to interfaces is bad design. I've fallen into this trap myself on a project before, which was adapted and had no TDD. I'd written tons of the things before I started to implement testing and suddenly realised that although it looked good it created a ton of test headaches.
Looking through the codebase this would be a significant change, however, it shouldn't be a breaking change as the external interfaces should not change at all in terms of how they are viewed by the external code.
With your point on extension methods, is that really a problem? If you’re generating a mock of IPublishedContent surely you can control what is returned by the ContentType property, so I’m failing to see the problem with that example.
Regarding overall unit testing against Umbraco I echo the frustration that it is simply so difficult to test components which were inherently designed for testability, especially Controllers.
I’ve previously asked about changing UmbracoContext.Current to expose an interface, allowing you to pass in an interface to represent it, but the problem with doing that is that UmbracoContext has a lot of internal members on it which are used by Umbraco internally (duh!). By making it an interface you no longer have the ability to create these internal members which means that Umbraco would have to either expose these currently internal members or change something about the way it’s designed.
I think this is actually a symptom of a larger challenge, the reliance on a Service Locator pattern. There are a lot of these ‘service bases’, UmbracoContext and ApplicationContext are examples, and these expose dependencies which are either directly consumed from the Service Locator, or in some cases they are passed into the component. This means you need to set up the Service Locator before you can consume the dependencies because sometimes you can pass in your dependencies to constructors and sometimes it’ll internally resolve them.
I’ve hit this problem a few times in Chauffeur, which is using the API in a way it was never intended, as I don’t use the CoreBootManager which does a lot of this initialisation for you.
I don’t really have a solution for how to fix unit testing easily as there’s a lot of touch points you’d need to hit (which you found out). Umbraco 5 showed us how over architecting a problem will make things just as bad, if not worse, and we want to avoid repeating history.
Instead this was just a collection of my random thoughts that can go along with the work Keith has undertaken.
--
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/de28816a-986b-47db-a54a-8b9449f8406b%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to a topic in the Google Groups "Umbraco development" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/umbraco-dev/BKgGSgeKEug/unsubscribe.
To unsubscribe from this group and all its topics, send an email to umbraco-dev...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/umbraco-dev/BLU402-EAS7764D022116E65FEEBA7A3EE530%40phx.gbl.
Basically, “internalizing” and tight coupling has to stop (completely!) and get refactored before we can unit-test everything. J
The core is developed with a philosophy of making things internal until the interface has been through a proof of concept phase and the team is sure the interface(s) won’t change. Sometimes, stuff will be made public when enough people ask for it.
However, sometimes the poc phase just never ends.
I for one wish everything would be public, mostly virtual and have interfaces. I’d rather go fix stuff that gets broken in updates, or even leave the sites with old versions alone.
(For one, I’m upgrading a project just now due to the Newtonsoft.Json upgrade. Finally!)
But I’m sure this won’t get better until version 9 or 10.
I’m hoping for a session about this at codegarden.
Lars-Erik
To view this discussion on the web visit https://groups.google.com/d/msgid/umbraco-dev/BLU402-EAS7764D022116E65FEEBA7A3EE530%40phx.gbl.
Is Ms Fakes available to normal subscribers now? I thought it was for ultimate only
--
You received this message because you are subscribed to a topic in the Google Groups "Umbraco development" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/umbraco-dev/BKgGSgeKEug/unsubscribe.
To unsubscribe from this group and all its topics, 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/57f0b811-3b68-4ac4-9d7d-da0df294a196%40googlegroups.com.