Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit-test WCF contracts match for sync / async?

WCF makes it easy to call services synchronously or asynchronously, regardless of how the service is implemented. To accommodate clients using ChannelFactory, services can even define separate sync/async contract interfaces. For example:

public interface IFooService
{
    int Bar();
}

[ServiceContract(Name = "IFooService")]
public interface IAsyncFooService
{
    Task<int> BarAsync();
}

This allows the client to reference either contract version, and WCF translates the actual API calls automatically.

One drawback to providing both contract versions is that they must be kept in-sync. If you forget to update one, the client may receive a contract mismatch exception at runtime.

Is there an easy way to unit test the interfaces to ensure they match from a WCF metadata perspective?

like image 541
Scott Wegner Avatar asked Jan 02 '14 16:01

Scott Wegner


2 Answers

You can retrieve the ContractDescription and use WsdlExporter to generate the WSDL. The output MetadataSet is XML serializable, so you can compare the representations for each contract version to ensure they match:

    [TestMethod]
    public void ContractsMatch()
    {
        // Arrange
        string expectedWsdl = this.GetContractString<IFooService>();

        // Act
        string actualWsdl = this.GetContractString<IAsyncFooService>();

        // Assert
        Assert.AreEqual(expectedWsdl, actualWsdl);
    }

    private string GetContractString<TContract>()
    {
        ContractDescription description = ContractDescription.GetContract(typeof(TContract));
        WsdlExporter wsdlExporter = new WsdlExporter();

        wsdlExporter.ExportContract(description);
        if (wsdlExporter.Errors.Any())
        {
            throw new InvalidOperationException(string.Format("Failed to export WSDL: {0}", string.Join(", ", wsdlExporter.Errors.Select(e => e.Message))));
        }

        MetadataSet wsdlMetadata = wsdlExporter.GetGeneratedMetadata();

        string contractStr;
        StringBuilder stringBuilder = new StringBuilder();
        using (XmlWriter xmlWriter = XmlWriter.Create(stringBuilder))
        {
            wsdlMetadata.WriteTo(xmlWriter);
            contractStr = stringBuilder.ToString();
        }

        return contractStr;
    }
like image 150
Scott Wegner Avatar answered Sep 29 '22 12:09

Scott Wegner


Your own answer is great. As BartoszKP points out it is more of an integration test, but this might be the best fit. You could argue that comparing two units (interfaces) to each other is not a unit test by definition.

The advantage of your approach is that you can be sure to verify what WCF makes from your classes. If you only want to test your own code, you could do something like that:

[TestMethod]
public void ContractsMatch()
{
    var asyncMethodsTransformed = typeof(IAsyncFooService)
        .GetMethods()
        .Select(mi => new 
        { 
            ReturnType = mi.ReturnType,
            Name = mi.Name,
            Parameters = mi.GetParameters()
        });
    var syncMethodsTransformed = typeof(IFooService)
        .GetMethods()
        .Select(mi => new
        {
            ReturnType = WrapInTask(mi.ReturnType),
            Name = Asyncify(mi.Name),
            Parameters = mi.GetParameters()
        });

    Assert.That(asyncMethodsTransformed, Is.EquivalentTo(syncMethodsTransformed));
}

The idea is that for each method in your IFooService you expect a method which has a similar signature with clearly defined transformations:

  • The name must contain a "Async" after the "I"
  • The return type must be a Task of the type found in the sync version.

The WrapInTask and Asyncify are left as exercise :-) If you like this suggestion I can expand on them.

By using a test like that you might constrain the code more than WCF does (I don't know the Async support very well). But even if it does you might want that to ensure some code consistency.

like image 20
chiccodoro Avatar answered Sep 29 '22 12:09

chiccodoro