1

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.

3
  • 1
    What kind of test it is when you are not asserting anything?
    – Euphoric
    Apr 22, 2015 at 9:51
  • I suspect you might be looking for the Type type. As in: servicestoTest.Add(typeof(Languages_Service)). It is a System.Type object and is meta data about the Language_Service type and you can instantiate an object from it.
    – radarbob
    Apr 23, 2015 at 4:06
  • @radarbob I implemented you idea in my 'pseudocode' above. The compiler says now that SoapHttpClientProtocol does not have a method ReadMultiple. How can I fix that?
    – lozsui
    May 4, 2015 at 13:42

2 Answers 2

1

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.
0

I modified Ben Aaronsons idea slighly:

public class NavisionSoapService : SoapHttpClientProtocol
{
    public object[] ReadMultiple([System.Xml.Serialization.XmlElementAttribute("filter")] object[] filter, string bookmarkKey, int setSize)
    {
        return this.Invoke("ReadMultiple", new object[] {filter,bookmarkKey,setSize});
    }
}

    [TestClass]
    public class SoapServiceGunSmokeTests
    {
        private void Test<TService>(string urlSuffix)
            where TService : NavisionSoapService, new()
        {
            string serviceUrl = Helper.GetBaseUrl() + urlSuffix;
            TService service = new TService();
            service.Credentials = Helper.GetCredentials();
            service.Url = serviceUrl;
            // something is fishy here: object [] filter = [System.Xml.Serialization.XmlElementAttribute("filter")] object[] filter;
            service.ReadMultiple(filter,null,0);
        }
    [TestMethod]
    public void PlaceOfWork()
    {
        Test<PlaceOfWork_Service>("PlaceOfWork");
    }
}

Now, I get: "The type 'PlaceOfWork_Service' cannot be used as type parameter 'TService' in the generic type or method 'MyProject.SoapServiceGunSmokeTests.Test(string)'. There is no implicit reference conversion from 'MyProject.WSPlaceOfWork.PlaceOfWork_Service' to 'MyProject.NavisionSoapService'.

Obviously, I am doing something wrong with the superclass NavisionSoapService. How do I have to do it?

Moreover, somehow I can not manage to import System.Xml.Serialization I get "The type or namespace name 'Xml' does not exist in the namespace 'System' (are you missing an assembly reference?)". I added System.Xml.Serialization (Version 4.0.0.0) as a reference.

Finally, I am doing something wrong by creating an instance of filter (see code above).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.