-
Testing dependency resolution
Here's a version of [Rob Moore's class](https://robdmoore.id.au/blog/2012/05/29/controller-instantiation-testing/) for testing dependency resolution for MVC controllers.
In our team at Frontiers, we have an Application Service Layer that is partially shared between a console application and a REST API built with ASP.NET MVC. So for us (and I think it's a common need) it is important also to test that the dependencies of the application layer itself are correctly resolved.
Since we didn't have any common ancestor for our Application Services, we created an empty interface; but then the Code Analysis complained that empty interfaces are not a good thing, so we ended up using an attribute.
We also use MsTest (business rule, alleviated with Fluent Assertions), and Ninject. And this is the result:
using System; using System.Collections.Generic; using System.Configuration; using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; using Ninject; namespace Frontiers.Ioc.Tests { /// <summary> /// This tests try to instantiate all available controllers and application services using Ninject. /// The tests will fail if some binding is missing or incorrectly configured. /// </summary> /// <remarks> /// We are instantiating controllers and application services because these constitute the /// surface of our dependency injection calls, that is, Ninject is only called directly to /// instantiate these classes. If Ninject were to be called to instantiate other classes, /// those should be also included here. /// </remarks> [TestClass] public class IocTests { [TestMethod] public void MvcControllerTest() { IKernel kernel = BuildKernel(); IEnumerable<Type> controllerTypes = typeof(BaseController) .Assembly .GetTypes() .Where(t => typeof(BaseController).IsAssignableFrom(t) && !t.IsAbstract); foreach (Type controllerType in controllerTypes) { kernel.Get(controllerType); } } [TestMethod] public void ApplicationServiceTest() { IKernel kernel = BuildKernel(); var controllerTypes = typeof(ApplicationServiceAttribute).Assembly.GetTypes() .Where(t => HasAttribute<ApplicationServiceAttribute>(t) && !t.IsAbstract); foreach (Type controllerType in controllerTypes) { kernel.Get(controllerType); } } private static bool HasAttribute<T>(Type t) where T : Attribute { return Attribute.GetCustomAttribute( t, typeof(T)) != null; } private IKernel BuildKernel() { // Here you must build your kernel and load any necessary modules. } } }
-
TFS 2013 Checkout override with a git branch
Another quick note, this time on TFS + git. When you queue a manual build you can specify what commit you want in a number of ways, using the "Checkout override" option. You can specify either a commit ID, a tag or a branch.
For the commit you paste the SHA-1, or a significant part of it, just as you do with the command line.
For the tag you just use the tag name.
For the branch you use the branch name, but there's a silly trick that drove me nuts until jessehouwing pointed it out for me: you must prepend
origin/
to your branch name. -
My new work at Frontiers
I just wanted to write a quick note about my new job at Frontiers. We are working on a new feature for the social network of Frontiers; I cannot talk much about it because it is not already live, but I can tell you that I am very excited about the challenges we are facing. Among other things we got some communication using RabbitMQ, API crawling & digestion processes that must be very scalable (because Frontiers has been recently acquired by Nature and we are going to inherit all its users!), a brand new bounded context that we have the opportunity of managing in a DDD way... I will try to be a little more talkative about this project than I were while at Electronic Arts.
-
Testing Web API and MVC routes altogether
WORKS! In my brand-new job at Electronic Arts we are fortunate enough to work with Microsoft's latest technologies. We are developing web applications and, as every good developer out there I like to test as many things as it is possible and practical.
In the last sprint I found myself with the problem of testing the routing configuration of a Web API REST service and also the routing configuration of some MVC controllers.
A DuckDuckGo search returned a great article at StrathWeb that solved the Web API side.
Searching then for the MVC side, I found that, unsurprisingly, the famous library MvcContrib has already a solution, but using a very different, fluent approach.
I already had my Web API tests written with Philip's class (Philip is the author of StrathWeb), so I thought I would create a similar class that solved the problem, borrowing the necessary code from MvcContrib test helpers.
The two classes were VERY similar, so I ended refactoring the common code in an abstract base class with an interface. Following is the code of the interface, the three classes and two sample tests that show how to build and use each one.
/// <summary> /// Route tester can test a route defined by URL, HTTP method and a lambda expression of a call to an action of a controller. /// </summary> public interface IRouteTester { /// <summary> /// Tests that an URL with an HTTP method is executed by an action of a controller. /// </summary> /// <typeparam name="TController">Controller type</typeparam> /// <typeparam name="TActionReturn">Return type of the action</typeparam> /// <param name="url">The URL to be tested</param> /// <param name="method">The HTTP method</param> /// <param name="expression">The lambda expression that defines an action of a controller</param> void TestRoute <TController, TActionReturn>( string url, HttpMethod method, Expression<Func <TController, TActionReturn>> expression); /// <summary> /// Tests that an URL with an HTTP method is executed by an action of a controller. /// </summary> /// <typeparam name="TController">Controller type</typeparam> /// <param name="url">The URL to be tested</param> /// <param name="method">The HTTP method</param> /// <param name="expression">The lambda expression that defines an action of a controller</param> void TestRoute <TController>( string url, HttpMethod method, Expression<Action <TController>> expression); }
public abstract class BaseRouteTester : IRouteTester { /// <summary> /// Tests that an URL with an HTTP method is executed by an action of a controller. /// </summary> /// <typeparam name="TController">Controller type</typeparam> /// <typeparam name="TActionReturn">Return type of the action</typeparam> /// <param name="url">The URL to be tested</param> /// <param name="method">The HTTP method</param> /// <param name="expression">The lambda expression that defines an action of a controller</param> public void TestRoute <TController, TActionReturn>(string url, HttpMethod method, Expression<Func <TController, TActionReturn>> expression) { this.TestRoute <TController>(url, method, GetMethodCall(expression)); } /// <summary> /// Tests that an URL with an HTTP method is executed by an action of a controller. /// </summary> /// <typeparam name="TController">Controller type</typeparam> /// <param name="url">The URL to be tested</param> /// <param name="method">The HTTP method</param> /// <param name="expression">The lambda expression that defines an action of a controller</param> public void TestRoute <TController>(string url, HttpMethod method, Expression<Action <TController>> expression) { this.TestRoute <TController>(url, method, GetMethodCall(expression)); } /// <summary> /// Set ups route data and tests that the URL matches with some route. /// </summary> /// <param name="url">The URL</param> /// <param name="method">The HTTP method</param> protected abstract void SetupAndTestRouteData(string url, HttpMethod method); /// <summary> /// Get a route value /// </summary> /// <param name="key"></param> /// <returns></returns> protected abstract object GetValue(string key); /// <summary> /// Is a route value defined? /// </summary> /// <param name="key"></param> /// <returns></returns> protected abstract bool HasValue(string key); /// <summary> /// Gets the name of the controller corresponding to the matched route /// </summary> /// <returns></returns> protected abstract string GetControllerName(); /// <summary> /// Gets the name of the action corresponding to the matched route /// </summary> /// <returns></returns> protected abstract string GetActionName(); /// <summary> /// Gets the <see cref="MethodCallExpression"/> for a expression corresponding to an <see cref="Action{T}"/> (void method) /// </summary> /// <typeparam name="T">Type of the parameters</typeparam> /// <param name="expression">The expression</param> /// <returns></returns> private static MethodCallExpression GetMethodCall <T>(Expression<Action <T>> expression) { return expression.Body as MethodCallExpression; } /// <summary> /// Gets the <see cref="MethodCallExpression"/> for a expression corresponding to an <see cref="Func{T,U}"/> (method returning a T) /// </summary> /// <typeparam name="T">Type of the parameters</typeparam> /// <typeparam name="U">Return type of the call</typeparam> /// <param name="expression">The expression</param> /// <returns></returns> private static MethodCallExpression GetMethodCall <T, U>(Expression<Func <T, U>> expression) { return expression.Body as MethodCallExpression; } /// <summary> /// Get the method name of a /// </summary> /// <param name="method"></param> /// <returns></returns> private string GetMethodName(MethodCallExpression method) { if (method != null) { return method.Method.Name; } throw new ArgumentException("Expression is wrong"); } /// <summary> /// Tests that an URL with an HTTP method is executed by an action of a controller. /// </summary> /// <typeparam name="TController">Controller type</typeparam> /// <param name="url">The URL to be tested</param> /// <param name="method">The HTTP method</param> /// <param name="methodCall">Method call expression</param> private void TestRoute <TController>(string url, HttpMethod method, MethodCallExpression methodCall) { // check route this.SetupAndTestRouteData(url, method); // check controller string expectedController = typeof(TController).Name; string actualController = this.GetControllerName(); if (expectedController != actualController) { throw new ControllerNotFoundException(expectedController, actualController); } // check action string actualAction = this.GetActionName(); string expectedAction = this.GetMethodName(methodCall); if (expectedAction != actualAction) { throw new ActionNotFoundException(expectedAction, actualAction); } // check parameters for (int i = 0; i < methodCall.Arguments.Count; i++) { ParameterInfo param = methodCall.Method.GetParameters()[i]; bool isReferenceType = !param.ParameterType.IsValueType; bool isNullable = isReferenceType || (param.ParameterType.UnderlyingSystemType.IsGenericType && param.ParameterType.UnderlyingSystemType.GetGenericTypeDefinition() == typeof(Nullable<>)); string controllerParameterName = param.Name; object actualValue = GetValue(controllerParameterName); object expectedValue = null; Expression expressionToEvaluate = methodCall.Arguments[i]; // If the parameter is nullable and the expression is a Convert UnaryExpression, // we actually want to test against the value of the expression's operand. if (expressionToEvaluate.NodeType == ExpressionType.Convert && expressionToEvaluate is UnaryExpression) { expressionToEvaluate = ((UnaryExpression)expressionToEvaluate).Operand; } switch (expressionToEvaluate.NodeType) { case ExpressionType.Constant: expectedValue = ((ConstantExpression)expressionToEvaluate).Value; break; case ExpressionType.New: case ExpressionType.MemberAccess: expectedValue = Expression.Lambda(expressionToEvaluate).Compile().DynamicInvoke(); break; } if (isNullable && (string)actualValue == string.Empty && expectedValue == null) { // The parameter is nullable so an expected value of '' is equivalent to null; continue; } // HACK: this is only sufficient while System.Web.Mvc.UrlParameter has only a single value. if (actualValue == UrlParameter.Optional || (actualValue != null && actualValue.ToString().Equals("System.Web.Mvc.UrlParameter"))) { actualValue = null; } if (expectedValue is DateTime) { actualValue = Convert.ToDateTime(actualValue); } else { expectedValue = expectedValue == null ? null : expectedValue.ToString(); } bool isOptional = methodCall.Method.GetParameters()[i].IsOptional; if ((actualValue == null) && isOptional) { return; } if (!Equals(actualValue, expectedValue)) { throw new ValueMismatchException(controllerParameterName, actualValue, expectedValue); } } } }
/// <summary> /// Class that allows to test a Web API route. /// </summary> public class RestRouteTester : BaseRouteTester { private readonly HttpConfiguration config; private IHttpControllerSelector controllerSelector; private HttpControllerContext controllerContext; private IHttpRouteData routeData; private HttpRequestMessage request; public RestRouteTester(HttpConfiguration conf) { this.config = conf; } /// <summary> /// Gets the name of the action corresponding to the matched route /// </summary> /// <returns></returns> protected override string GetActionName() { if (this.controllerContext.ControllerDescriptor == null) { this.GetControllerName(); } var actionSelector = new ApiControllerActionSelector(); var descriptor = actionSelector.SelectAction(this.controllerContext); return descriptor.ActionName; } /// <summary> /// Gets the name of the controller corresponding to the matched route /// </summary> /// <returns></returns> protected override string GetControllerName() { var descriptor = this.controllerSelector.SelectController(this.request); this.controllerContext.ControllerDescriptor = descriptor; return descriptor.ControllerType.Name; } /// <summary> /// Set ups route data and tests that the URL matches with some route. /// </summary> /// <param name="url">The URL</param> /// <param name="method">The HTTP method</param> protected override void SetupAndTestRouteData(string url, HttpMethod method) { this.request = new HttpRequestMessage(method, url); this.routeData = config.Routes.GetRouteData(this.request); if (this.routeData == null) { throw new RouteNotFoundException(method, url); } this.request.Properties[HttpPropertyKeys.HttpRouteDataKey] = routeData; this.controllerSelector = new DefaultHttpControllerSelector(config); this.controllerContext = new HttpControllerContext(config, routeData, this.request); } /// <summary> /// Get a route value /// </summary> /// <param name="key"></param> /// <returns></returns> protected override object GetValue(string key) { foreach (var routeValueKey in this.routeData.Values.Keys) { if (string.Equals(routeValueKey, key, StringComparison.InvariantCultureIgnoreCase)) { if (this.routeData.Values[routeValueKey] == null) { return null; } return this.routeData.Values[routeValueKey].ToString(); } } return null; } /// <summary> /// Is a route value defined? /// </summary> /// <param name="key"></param> /// <returns></returns> protected override bool HasValue(string key) { return this.routeData.Values.ContainsKey(key); } }
public class WebRouteTester : BaseRouteTester { private readonly RouteCollection routeCollection; private RouteData routeData; public WebRouteTester(RouteCollection routeCollection) { this.routeCollection = routeCollection; } /// <summary> /// Set ups route data and tests that the URL matches with some route. /// </summary> /// <param name="url">The URL</param> /// <param name="method">The HTTP method</param> protected override void SetupAndTestRouteData(string url, HttpMethod method) { this.routeData = routeCollection.GetRouteData(new FakeHttpContext(url, method.ToString())); if (this.routeData == null) { throw new RouteNotFoundException(method, url); } } /// <summary> /// Get a route value /// </summary> /// <param name="key"></param> /// <returns></returns> protected override object GetValue(string key) { foreach (var routeValueKey in this.routeData.Values.Keys) { if (string.Equals(routeValueKey, key, StringComparison.InvariantCultureIgnoreCase)) { if (this.routeData.Values[routeValueKey] == null) { return null; } if (this.routeData.Values[routeValueKey].GetType().Name == "UrlParameter") { return null; } return this.routeData.Values[routeValueKey]; } } return null; } /// <summary> /// Is a route value defined? /// </summary> /// <param name="key"></param> /// <returns></returns> protected override bool HasValue(string key) { return routeData.Values.ContainsKey(key); } /// <summary> /// Gets the name of the controller corresponding to the matched route /// </summary> /// <returns></returns> protected override string GetControllerName() { return this.GetValue("controller") + "Controller"; } /// <summary> /// Gets the name of the action corresponding to the matched route /// </summary> /// <returns></returns> protected override string GetActionName() { return this.GetValue("action").ToString(); } }
[TestClass] public class WebRoutingTests { private RouteCollection routeCollection; [TestInitialize] public void Initialize() { this.routeCollection = new RouteCollection(); WebRouteConfig.RegisterRoutes(routeCollection); } [TestMethod] public void PlaylistController_GetTracksOffset() { TestRoute("~/playlist/45/GetTracks/56", HttpMethod.Get, (PlaylistController tc) => tc.GetTracks(45, 56, 100)); } #region Private methods private void TestRoute <TController, TActionReturn>(string url, HttpMethod method, Expression<Func <TController, TActionReturn>> expression) { this.routeTester.TestRoute(url, method, expression); } private void TestRoute <TController>(string url, HttpMethod method, Expression<Action <TController>> expression) { this.routeTester.TestRoute(url, method, expression); } #endregion }
- 3<
-
4
-
•