The solution we put forward looks pretty complete so far: we’ve gained control over the UI so we could run automated tests and we’ve gained control over the front server so we could arbitrarily mock any of the backend systems. And we can do all of this from with a standard unit test. But there’s still more to do.
One unexpected problem came we tried to test some wizards that called a lot of other code that was outside of our own project. We thought we had a pretty good idea of what should happen, but every time we ran the test is returned with no results. We started tracing the SQL server and tracing the web service calls but still could find nothing wrong. When we used the UI manually we saw results, but if we ran it through the unit test it still returned nothing.
Digging down through the layers we discovered that actually a worker thread was used to retrieve the data, and that in fact the UI thread was not waiting for the result before continuing. Manually you really didn’t notice the delay between the screen building and the results flowing in, but this delay proved crucial for our test.
The way we handled this was to expose the background worker on the form using a PrivateObject. We could not expose it directly as we had done with our own code, as this code was beyond our control. For those of you not familiar with PrivateObject: this is a part of the Microsoft.VisualStudio.TestTools.UnitTesting namespace and allows you to wrap any object to expose all its private members in a very easy way during testing.
The second problem we ran across is much more fundamental: serializing our own complex types. To explain the issue I have to take you back to the
previous part of this series by my colleague Peter Notenbaert. In order to get the server components into process we have to serialize and then deserialize all reference type objects, emulating the web service, so that changes to these object in the server side code will not affect the client side objects passed to it for processing.
I should mention at this point that in this particular system we’re still working with WSE, and not yet with WCF. By default all complex objects will be proxyed on the client by generated classes even if you could reference he types used on the server side directly. This means that for anything but the basic set of .Net runtime types we have a serious problem: the web method on the server requires a parameter of type A, but the client side proxy will use a completely different proxy type A’ that has exactly the same structure, but does not even share an interface with the original type A.
As we want the client proxy and the web service class to share an interface we had serious problem. In fact to start with we had to implement special overloaded methods in a partial class on the client to enable us to implement the interface we’d extracted from the web service. Doing it the other way around would require a reference from the server to the client, which would be unthinkable.
As luck would have it, just when we were struggling with this problem another Euricom colleague Stijn Liesenborghs almost by accident mentioned the answer: System.Xml.Serialization.Advanced. SchemaImporterExtension.
He had used this on a project of his to get the proxy generator in Visual Studio not to generate its own type where you had defined one of your own that could be referenced by the client. This very well written
article by Jelle Druyts explains it much better than I can do in just a few lines here.
This extension enabled us to have the same complex types server side and client side just by putting them into a shared assembly together with our interfaces. Now finally we had a complete solution to share an interface on both sides of the web service, so that the web service and its proxy could be interchanged by using Unity and the shared interface.
There is still a limitation when it comes to things like generic list of a type being proxies as arrays, but other than that this works almost transparently.
I will devote another entry to the other uses the SchemaImporterExtension can be put to.
By Nick Verschueren, .Net Solutions Architect