C# – Generics in C# Test Class

cgenerics

I'm learning about unit testing in C#. Languages_Service and PlaceOfWork_Service are classes for SOAP services which Microsoft Navision generated for me. There are several methods that are very similar (see the two examples below).

[TestMethod]
public void Languages()
{
    string serviceUrl = Helper.GetBaseUrl() + "Languages";
    Languages_Service service = new Languages_Service();
    service.Credentials = Helper.GetCredentials();
    service.Url = serviceUrl;
    Languages_Filter[] filter = { new Languages_Filter() };
    service.ReadMultiple(filter, null, 0);
}
[TestMethod]
public void PlaceOfWork()
{
    string serviceUrl = Helper.GetBaseUrl() + "PlaceOfWork";
    PlaceOfWork_Service service = new PlaceOfWork_Service();
    service.Credentials = Helper.GetCredentials();
    service.Url = serviceUrl;
    PlaceOfWork_Filter[] filter = { new PlaceOfWork_Filter() };
    service.ReadMultiple(filter, null, 0);
}

Method ReadMultiple of Language_Service looks like so:

public Languages[] ReadMultiple([System.Xml.Serialization.XmlElementAttribute("filter")] Languages_Filter[] filter, string bookmarkKey, int setSize)
    {
        object[] results = this.Invoke("ReadMultiple", new object[] {
        filter,
        bookmarkKey,
        setSize});
     return ((Languages[])(results[0]));}

Method ReadMultiple of PlaceOfWork_Service looks like so:

public PlaceOfWork[] ReadMultiple([System.Xml.Serialization.XmlElementAttribute("filter")] PlaceOfWork_Filter[] filter, string bookmarkKey, int setSize) {
        object[] results = this.Invoke("ReadMultiple", new object[] {
                    filter,
                    bookmarkKey,
                    setSize});
        return ((PlaceOfWork[])(results[0]));
    }

How could one write something like the (pseudo) code below?

[TestMethod]
public void GunAndSmokeTestAllSerivces()
{
    Dictionary<String, List<Type>> servicesToTest = new Dictionary<String, List<Type>>();
    servicesToTest.Add("Languages", new List<Type>(new Type[] { typeof(Languages_Service), typeof(Languages_Filter) }));
    servicesToTest.Add("PlaceOfWork", new List<Type>(new Type[] { typeof(PlaceOfWork_Service), typeof(PlaceOfWork_Filter) }));
    foreach (KeyValuePair<String, List<Type>> entry in servicesToTest)
    {
        var t1 = entry.Value.ToArray()[0];
        var t2 = entry.Value.ToArray()[1];
        SoapHttpClientProtocol service = (SoapHttpClientProtocol)Activator.CreateInstance(t1);
        service.Credentials = Helper.GetCredentials();
        service.Url = Helper.GetBaseUrl() + entry.Key;
        object[] filter = { (object)Activator.CreateInstance(t2) };
        service.ReadMultiple(filter, null, 0);
    }
}

Languages_Service and PlaceOfWork_Service are subclasses of System.Web.Services.Protocols.SoapHttpClientProtocol.

Best Answer

You probably don't want to do exactly what you describe because then you'd have a single test method covering multiple test cases. This isn't great because it means your tests lose granularity, so:-

  • It becomes unclear when a test fails where exactly the bug may lie, e.g. which `Service.
  • There's a greater risk of one reason for failure masking another. i.e. there are actually multiple bugs, but because the test fails after the first one, you're not aware of the rest.

A simpler and probably more applicable solution is just to do what you normally do when there's repeated code like this- pull it out into a parameterized method:

private void Test<TService,TFilter>(string urlSuffix) 
    where TService : SoapHttpClientProtocol, new() 
    where TFilter : new() 
{
    string serviceUrl = Helper.GetBaseUrl() + urlSuffix;
    TService service = new TService();
    service.Credentials = Helper.GetCredentials();
    service.Url = serviceUrl;
    TFilter[] filter = { new TFilter() };
    service.ReadMultiple(filter, null, 0);
}

[TestMethod]
public void PlaceOfWork()
{
    Test<PlaceOfWork_Service,PlaceOfWork_Filter>("PlaceOfWork");
}

That's the basic version, just to give you a rough idea. As the comments said this seems incomplete without assertions, and there may be better ways to split this into multiple methods. You should also come up with better names based on your understanding of what exactly these tests are for.

A few issues to take note of:

  • This won't work out of the box because ReadMultiple isn't a method on SoapHttpClientProtocol. You'll need a superclass or interface with this method, and have that as a constraint on your TService generic parameter.
  • Likewise, TFilter may need some similar constraint
  • If the above two prove to be a problem, you could leave your individual test methods in charge of calling that ReadMultiple line, or they could provide an Action<TService,TFilter[]> or similar
  • If you can't actually keep to that new() constraint, you'll instead want to remove it and have a TService parameter on the method, and leave the individual test in charge of doing the construction.