Unit testing workflows became a lot easier in WF4. The basic approach is the same as for normal code classes except for a few differences which we will cover in the posts in this series.
Lets start off with a simple code activity which will return the time in a given city.
public class GetCityTime : CodeActivity<DateTime>{ [RequiredArgument] public InArgument<string> City { get; set; } public InArgument<DateTime> LocalTime { get; set; } protected override DateTime Execute(CodeActivityContext context) { string city = City.Get(context); TimeZoneInfo timeZoneInfo = GetTimeZoneForCity(city); if (timeZoneInfo == null) { throw new ArgumentOutOfRangeException(string.Format("Could not find timezone matching city '{0}'", city)); } return TimeZoneInfo.ConvertTime(LocalTime == null ? DateTime.Now : LocalTime.Get(context), timeZoneInfo); } private TimeZoneInfo GetTimeZoneForCity(string city) { return TimeZoneInfo.GetSystemTimeZones() .Where( timezone => timezone.DisplayName.ToUpperInvariant().Contains(city.ToUpperInvariant())) .FirstOrDefault(); } }
So lets start to build our first test. The WorkflowInvoker class allows us to execute a workflow activity in a blocking manner, meaning that it will execute the activity and wait for completion before continuing. We begin by testing that the City argument in indeed a required argument. This is always a good place to start and will make you think about the validations in your activity.
[ExpectedException(typeof(ArgumentException))] [TestMethod] public void CityIsARequiredArgument() { WorkflowInvoker.Invoke(new GetCityTime()); }
Next we are going to check that providing an invalid value for City will indeed throw an exception.
[ExpectedException(typeof(ArgumentOutOfRangeException))] [TestMethod] public void InvalidCityThrowsArgumentException() { WorkflowInvoker.Invoke(new GetCityTime() {City = "BlaBlaBla"}); }
The last test is to check the actual execution. A handy feature of the WorkflowInvoker generic static methods are that you can execute an Activity<T> and the return signature of the WorkflowInvoker.Invoke method will be the Result type of our Activity (T).
[TestMethod] public void ExecuteConvertsTimeToTargetCity() { DateTime currentLocalTime = DateTime.Now; string cityName = "London"; TimeZoneInfo timeZoneInfo = TimeZoneInfo.GetSystemTimeZones().Where( timeZone => timeZone.DisplayName.ToUpperInvariant().Contains(cityName.ToUpperInvariant())).First(); DateTime result = WorkflowInvoker.Invoke(new GetCityTime() { City = cityName, LocalTime = new InArgument<DateTime>(currentLocalTime) }); Assert.AreEqual(TimeZoneInfo.ConvertTime(currentLocalTime, timeZoneInfo), result); }