Главная страница   /   9.7. Тестирование поведения маршрута (ASP.NET MVC 4 в действии

ASP.NET MVC 4 в действии

ASP.NET MVC 4 в действии

Джеффри Палермо

9.7. Тестирование поведения маршрута

В предыдущем разделе вы видели, как можно случайно нарушить схему маршрутизации приложения, и как использовать Route Debugger для обнаружения возникших проблем во время выполнения. Но можно также писать модульные тесты для роутов, которые в первую очередь будут предотвращать возникновение таких проблем. В этом разделе мы рассмотрим, как тестировать обработку входных роутов, а также генерацию исходящих роутов.

Тестирование входящих роутов

По сравнению с остальной частью ASP.NET MVC Framework, тестирование роутов нельзя назвать легкой или интуитивной работой из-за количества абстрактных классов, для которых необходимо использовать mock-технологию. Чтобы сделать это вручную, потребуется много кода настроек, как показано в следующем листинге.

Листинг 9-11: Сложный способ тестирования роутов
using System.Web;
using System.Web.Routing;
using NUnit.Framework;
using Rhino.Mocks;
namespace RoutingSample.Tests
{
	[TestFixture]
	public class NotUsingTestHelper
	{
		[Test]
		public void root_matches_home_controller_index_action()
		{
			const string url = "~/";

			var request = MockRepository.GenerateStub<HttpRequestBase>();
			request.Stub(x => x.AppRelativeCurrentExecutionFilePath).Return(url).Repeat.Any();
			request.Stub(x => x.PathInfo).Return(string.Empty).Repeat.Any();

			var context = MockRepository.GenerateStub<HttpContextBase>();
			context.Stub(x => x.Request).Return(request).Repeat.Any();

			RouteTable.Routes.Clear();
			MvcApplication.RegisterRoutes(RouteTable.Routes);

			var routeData = RouteTable.Routes.GetRouteData(context);
			Assert.That(routeData.Values["controller"],
			Is.EqualTo("Home"));
			Assert.That(routeData.Values["action"],
			Is.EqualTo("Index"));
		}
	}
}

Строка 15-20: Настройка mock-запроса

Строка 22-23: Регистрация роутов

Если бы все тесты роутов выглядели так, как показано в листинге 9-11, никто бы даже и не потрудился их тестировать. Специфические заглушки на HttpContextBase и HttpRequestBase тоже не стали удачным решением. Они заглядывали внутрь инструмента Red Gate’s Reflector, чтобы выяснить, для чего использовать mock. Это неправильное поведение для тестируемой платформы!

К счастью, проект MvcContrib включает в себя удобный и быстрый API для тестирования роутов, который значительно упрощает работу. Для начала мы должны установить сборку MvcContrib.TestHelper с помощью команды Install-Package MvcContrib.Mvc3.TestHelper-ci в NuGet Package Manager Console, как показано на рисунке 9-9.

Рисунок 9-9: Установка MvcContrib Test Helper через NuGet

Следующий листинг представляет собой тот же тест, но с использованием расширений MvcContrib для тестирования роутов.

Листинг 9-12: Чистое тестирование роутов при помощи проекта TestHelper MvcContrib
[TestFixtureSetUp]
public void FixtureSetup()
{
	RouteTable.Routes.Clear();
	MvcApplication.RegisterRoutes(RouteTable.Routes);
}
[Test]
public void root_maps_to_home_index()
{
	"~/".ShouldMapTo<HomeController>(x => x.Index());
}

Строка 5: Регистрация роутов приложения

Строка 10: Утвердить URL для соответствия с действием

Начнем с регистрации роутов нашего приложения в TestFixtureSetUp с помощью статического метода RegisterRoutes из Global.asax. Фактически сам тест выполняется методами расширения и лямбда-выражениями. Внутри тестового вспомогательного метода MvcContrib есть метод расширения для класса string, который создает экземпляры RouteData на основе параметров URL. Класс RouteData имеет метод расширения для подтверждения того, что значения роута соответствуют контроллеру и действию.

В листинге 9-12 можно увидеть, что имя контроллера выводится из аргумента универсального типа в вызове метода ShouldMapTo<TController>(). Действие тогда определяется с помощью лямбда-выражения. Выражение разбивается так, чтобы получить вызов метода (действия) и любые аргументы, которые будут ему переданы. Аргументы сопоставляются со значениями роута. Более подробную информацию об этих расширениях для тестирования роутов можно найти на сайте MvcContrib http://mvccontrib.org.

Теперь пришло время применить их к правилам маршрутизации нашего магазина и убедиться, что мы охватили все необходимые случаи.

Листинг 9-13: Тестирование роутов из нашего примера
using System.Web.Routing;
using MvcContrib.TestHelper;
using NUnit.Framework;
using RoutingSample.Controllers;
namespace RoutingSample.Tests
{
	[TestFixture]
	public class UsingTestHelper
	{
		[TestFixtureSetUp]
		public void FixtureSetup()
		{
			RouteTable.Routes.Clear();
			MvcApplication.RegisterRoutes(RouteTable.Routes);
		}
		[Test]
		public void root_maps_to_home_index()
		{
			"~/".ShouldMapTo<HomeController>(x => x.Index());
		}
		[Test]
		public void privacy_should_map_to_home_privacy()
		{
			"~/privacy".ShouldMapTo<HomeController>(x => x.Privacy());
		}
		[Test]
		public void products_should_map_to_catalog_index()
		{
			"~/products".ShouldMapTo<CatalogController>(x => x.Index());
		}
		[Test]
		public void product_code_url()
		{
			"~/products/product-1".ShouldMapTo<CatalogController>(x => x.Show("product-1"));
		}
		[Test]
		public void product_buy_url()
		{
			"~/products/product-1/buy".ShouldMapTo<CatalogController>(x => x.Buy("product-1"));
		}
		[Test]
		public void basket_should_map_to_catalog_basket()
		{
			"~/basket".ShouldMapTo<CatalogController>(x => x.Basket());
		}
		[Test]
		public void checkout_should_map_to_catalog_checkout()
		{
			"~/checkout".ShouldMapTo<CatalogController>(x => x.CheckOut());
		}
		[Test]
		public void _404_should_map_to_error_notfound()
		{
			"~/404".ShouldMapTo<ErrorController>(x => x.NotFound());
		}
		[Test]
		public void ProductsByCategory_MapsToWebFormPage()
		{
			"~/Products/ByCategory".ShouldMapToPage("~/ProductsByCategory.aspx");
		}
	}
}

Каждый из этих простых тестов использует интерфейс тестирования NUnit. Они также используют метод расширения ShouldMapTo<T> из MvcContrib.TestHelper.

Примечание

Заключительный тест использует другой метод из MvcContrib - TestHelper. Метод ShouldMapToPage гарантирует, что URL соответствует конкретной странице Web Forms. Это бы вызвало ошибку маршрутизации, которую мы рассмотрели ранее. Если у вас есть юнит тесты для роутов, вы потратите меньше времени на их отладку.

Запустив этот пример, мы увидим, что все наши роуты работают нормально. На рис 9-10 показаны результаты в ReSharper test runner (могут выглядеть несколько по-другому, в зависимости от вашей тестируемой платформы и версии программы).

Рисунок 9-10: Результаты тестирования роутов в ReSharper test runner

Примечание

В листинге 9-13 мы создали для каждого правила отдельный тест. Может быть заманчиво объединить все эти одностроковые тесты в один, но не забывайте, как важно понимать то, почему тест не работает. Если вы допустили ошибку, только отдельные тесты перестанут работать, и вам будет легче понять причину, нежели когда перестанет работать один тест типа test_all_routes().

Вооружившись этими тестами, вы можете свободно изменять правила роутов, не беспокоясь о том, что после этого у вас на сайте перестанут работать ссылки. Представьте себе, что ссылки на товары на Amazon.com неожиданно перестали бы работать из-за опечатки в каком-нибудь правиле роута... Не позволяйте такому случиться с вашим сайтом. Гораздо легче писать автоматизированные тесты для сайта, чем вручную проводить исследовательское тестирование для каждого релиза.

Тестирование исходящих роутов

В тестировании роутов есть один важный аспект, которому мы до сих пор уделяли мало внимания: исходящая маршрутизация. Как было определено ранее, исходящая маршрутизация относится к URL, которые генерируются платформой по данному набору значений роута. Вспомогательные методы для тестирования генерации исходящих роутов также включены в проект MvcContrib и показаны в следующем листинге.

Листинг 9-14 : Тестирование генерации исходящих URL
[TestFixtureSetUp]
public void FixtureSetup()
{
	RouteTable.Routes.Clear();
	MvcApplication.RegisterRoutes(RouteTable.Routes);
}
[Test]
public void Generates_products_url()
{
	OutBoundUrl.Of<CatalogController>(x => x.Show("my-product-code"))
		.ShouldMapToUrl("/products/my-product-code");
}

В этом примере мы тестируем роут для страницы товара нашего приложения. С помощью метода OutBoundUrl.Of мы можем убедиться, что при передаче в движок маршрутизации имени контроллера catalog, действия show и товарного кода my-product-code он будет генерировать URL /products/my-product-code.

Теперь, когда мы рассмотрели исчерпывающий пример схем маршрутизации, мы можем начинать создавать роуты для своих собственных приложений и получать удобные, доступные URL. Мы также познакомились с некоторыми полезными расширениями для модульного тестирования, которые помогают гораздо проще тестировать входящие роуты.