Peter Goodman bio photo

Peter Goodman

A software engineer and leader living in Auckland building products and teams. Originally from Derry, Ireland.

Twitter Google+ LinkedIn Github

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);
}