Web services have similar problems to databases, for example
the service might be down or unavailable when the tests are executing causing
them to fail. If the web service is external and not under our control,
we are then faced with a whole host of new problems. If an API key is
required to access the service, for example Microsoft Live API, then you have the
problem of managing the keys in the different environments or using a single
key for everything. Along with this, there might be a limit on the number of
times you can access the web service or you have to pay per request, not great
if you are using this as part of your test suite. Finally, each request
depends on the bandwidth and the speed of the external site, causing our tests
to slow down our entire test suite execution.
Based on the previous requirements, we now
are required to integrate an external payment service to handle the
credit card processing for customer orders. While we are implementing this
to go over a soap web service, the principals are the same for any external
service. In the solution, which you can download at the top, I have
included a web service; it does not contain any logic, but it serves as a
placeholder.
The first step is to add a reference to the web service
using Visual Studio. This creates a local implementation which we can call
to access the method on the web service. As this code is auto generated,
we can trust that it works and we do not need to test it. However, we do
need to test the logic for accessing the service.
To implement this system, I am going to use a proxy object
which will implement an interface and call the real web service. We then
have a processor object which calls the methods on the proxy. This means
that when we are testing the system, we can mock the proxy so the real web
service is never called.
The first test we need to write, as shown in Listing 16, is one
to make sure that if a proxy object is called into the constructor, it sets the
internal variable. By testing this we can be confident that even a proxy object
can be called.
Listing 16
[Test]
public void ProxyCanBeSetAsAParameter()
{
PaymentProcessingProxy proxy = new PaymentProcessingProxy();
PaymentProcessor pp = new PaymentProcessor(proxy);
Assert.IsNotNull(pp.wsProxy);
}
The implementation and interface look like Listing 17.
Listing 17
internal IPaymentProcessing wsProxy;
public PaymentProcessor(IPaymentProcessing proxy)
{
wsProxy = proxy;
}
public interface IPaymentProcessing
{
bool TakePayment(int paymentId, int customerId, double amount);
}
Now we can write a test to verify that
PaymentProcessor executes the TakePayment method on
the IPaymentProcessing object. If we have code which calls the web
service, then it will also need to be tested using the PaymentProcessor and the
mock version of IPaymentProcessing.
Listing 18
[Test]
public void TakePaymentViaPaymentProcessorUsingMockService()
{
MockRepository mocks = new MockRepository();
IPaymentProcessing mockProxy = mocks.CreateMock<IPaymentProcessing>();
using(mocks.Record())
{
Expect.Call(mockProxy.TakePayment(1, 1,
10.0)).IgnoreArguments().Return(true);
}
using(mocks.Playback())
{
PaymentProcessor pp = new PaymentProcessor(mockProxy);
bool result = pp.TakePayment(1, 1, 10.0);
Assert.IsTrue(result);
}
}
So that is great, we have a PaymentProcessor which executes
TakePayment correctly on an IPaymentProcessing object without having to
interact with an external dependencies. Now we just need to implement the
real PaymentProcessingProxy which can connect to the web service.
Listing 19 shows the test. As with the Database, we have
refactored the implementation enough so we can mock the code within the other
parts of the code, however, to test the actual production code we need to
execute against the real implementation.
Listing 19
[Test]
public void PaymentProcessorProxyExecutesCorrectlyUsingRealService()
{
PaymentProcessingProxy proxy = new PaymentProcessingProxy();
bool result = proxy.TakePayment(1, 1, 10.0);
Assert.IsTrue(result);
}
The first time I executed this test it failed with the
following error:
"TestCase
'WebServiceTests.PaymentProcessorProxyExecutesCorrectlyUsingRealService'
failed: Unable to connect to the remote server
System.Net.WebException
Message: Unable to connect to the remote server"
This error occurred because the web service was not running.
If we were connecting to this directly within our business code and not using
mock objects, the test would fail without ever testing the business logic. By
using mock objects, we only have a single failing test in which we can easily
identify the reason for failing, especially if the other two tests are passing,
as the only thing it does is execute the external service.
The implementation is shown below.
Listing 20
public class PaymentProcessingProxy : IPaymentProcessing
{
public bool TakePayment(int paymentId, int accountId, double amount)
{
PaymentService ps = new PaymentService.PaymentService();
return ps.TakePaymentFromAccount(paymentId, accountId, amount);
}
}
This completes our implementation of the web service. We
have three tests, two checking the PaymentProcessor and one checking our real
proxy implementation. Our system is also much more flexible as none of the
system is dependent on the actual web service, instead it depends on the
PaymentProcessor and an object which implements IPaymentProcessing, but this
could be a database or completely different web service.