Beginning to Mock with Rhino Mocks and MbUnit - Part 2
page 3 of 5
by Ben Hall
Feedback
Average Rating: This article has not yet been rated.
Views (Total / Last 10 Days): 36729/ 43

Web Service

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.


View Entire Article

User Comments

Title: saf   
Name: saf
Date: 2012-12-06 12:46:37 AM
Comment:
saf
Title: SE   
Name: Rakib
Date: 2010-05-12 6:29:21 AM
Comment:
Its a very good article
Title: CannotConnectToDatabaseException   
Name: Ben Hall
Date: 2008-04-03 10:12:17 AM
Comment:
CannotConnectToDatabaseException is a custom exception I created in my production code (what is being tested) because I couldn't create a SqlException to raise from that side :)
Title: Listing 5: Expect.Call GetAllBooks but call GetBooksWithPriceBelow   
Name: Bert Vanpeteghem
Date: 2008-01-15 5:13:50 AM
Comment:
A question on Listing 5:
In the record-section: there is Expect.Call(mockDataAccess.GetAllBooks())...
And in the playback-section:
bookService.GetBooksWithPriceBelow(10);

Shouldn't the call to GetBooksWithPriceBelow also be in the record?
Title: S/W Engineer   
Name: Bill Campbell
Date: 2007-10-15 12:52:47 PM
Comment:
Here's a link to the first one:

http://aspalliance.com/1400_beginning_to_mock_with_rhino_mocks_and_mbunit__part_1
Title: Beginning to Mock Part 2 Needs a Part 1 Link   
Name: Roger Jennings
Date: 2007-10-15 11:01:12 AM
Comment:
There's no obvious link to Part 1 in the introduction, which recommends it for readers unfamiliar with mock frameworks.

--rj

Product Spotlight
Product Spotlight 





Community Advice: ASP | SQL | XML | Regular Expressions | Windows


©Copyright 1998-2024 ASPAlliance.com  |  Page Processed at 2024-03-28 6:48:54 PM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search