Recipe20.4.Applying Mock Components to Support Unit Testing


Recipe 20.4. Applying Mock Components to Support Unit Testing

Problem

You want to unit test your own component in isolation by providing a test harness implementation of a component that your component uses.

Solution

Create a mock implementation of the external component that your component depends on. Create an aspect that applies the mock component implementation in place of the real component.

When unit testing is complete, use a separate AspectJ build configuration file to switch out the testing aspect so the real implementation can be used again.

Discussion

When you are creating your software components, unit test those components in isolation to ensure that the component works as it should. However, when your component relies on other components, it can be tricky and error prone to manually unhook all of the dependencies to the external components for the unit testing and to restore those hooks when unit testing is complete.

Example 20-9 shows a typical situation where MyComponent is the component to be tested, and it has dependencies on an external implementation of ThirdPartyComponentInterface . The real implementation of ThirdPartyComponentInterface is retrieved by making a call to the factory method ThirdPartyFactory.getThirdPartyComponent().

Example 20-9. Creating and using an external component
package com.ourcompany; import com.thirdparty.ThirdPartyComponentFactory; import com.thirdparty.ThirdPartyComponentInterface; public class MyComponent implements MyComponentInterface {    private ThirdPartyComponentInterface thirdPartyComponent;             public MyComponent( )    {       this.thirdPartyComponent = ThirdPartyComponentFactory.                                  getThirdPartyComponent( );       System.out.println("Component found " + thirdPartyComponent);    }             public void foo( )    {       System.out.println("Inside MyComponent.foo( )");       this.thirdPartyComponent.bar( );    } }

Example 20-9 shows the use of a factory class, but this is not a mandatory feature of this recipes solution. Overriding the instantiation of a third-party component directly is possible using the same techniques as shown in Recipe 20.2 without the need for a factory.


To run a unit test against MyComponent in isolation, you will need to override the ThirdPartyComponent implementation so you will not confuse the test results by including the real external component in the test. One strategy is to apply a mock component that overrides the real component implementation manually, as shown in Example 20-10.

Example 20-10. Creating a mock class and then manually amending your component to use the new mock object
package test.com.ourcompany; import com.thirdparty.ThirdPartyComponentInterface; public class MockThirdPartyComponent implements ThirdPartyComponentInterface  {    public void bar( )     {       System.out.println("Inside MockThirdPartyComponent.bar( )");    } } package com.ourcompany; import com.thirdparty.ThirdPartyComponentFactory; import com.thirdparty.ThirdPartyComponentInterface; import test.com.ourcompany.MockThirdPartyComponent; public class MyComponent implements MyComponentInterface {    private ThirdPartyComponentInterface thirdPartyComponent;             public MyComponent( )    {       // Manually commented out for unit testing purposes       // this.thirdPartyComponent =        //    ThirdPartyComponentFactory.getThirdPartyComponent( );       // Replaced with mock object       this.thirdPartyComponent = new MockThirdPartyComponent( );       System.out.println("Component found " + thirdPartyComponent);    }             public void foo( )    {       System.out.println("Inside MyComponent.foo( )");       this.thirdPartyComponent.bar( );         } }

The MockThirdPartyComponent is under your control, unlike the real implementation of the ThirdPartyComponentInterface, so you can tailor the component to test your MyComponent correctly. The MyComponent is no longer reliant on the real implementation of the ThirdPartyComponentInterface, so you can safely conclude that any problems that occur when it is tested are your problems.

After your unit testing is complete and you are satisfied that MyComponent is working correctly, the mock component can be removed and the original code uncommented. Using this method, switching back to the real implementation of ThirdPartyComponentInterface is another manual and potentially error-prone task.

Though the solution in Example 20-10 works fine, it can be a difficult approach to manage if your component uses more than a few interfaces. Using an aspect-oriented alternative, an aspect can be created that intercepts the creation of the ThirdPartyComponent and overrides the returned object with your mock object implementation, as shown in Example 20-11.

Example 20-11. Using an aspect to apply a mock object for isolation unit testing purposes
package test.com.ourcompany; import com.thirdparty.*; public aspect MockThirdPartyComponentAspect  {    public pointcut catchThirdPartyConstructor( ) :        call(ThirdPartyComponentInterface ThirdPartyComponentFactory.                getThirdPartyComponent( ));             Object around( ) : catchThirdPartyConstructor( )    {       return new MockThirdPartyComponent( );    }

The catchThirdPartyConstructor() pointcut catches calls to the ThirdPartyFactory.getThirdPartyComponent( ) method and uses the associated around( ) advice to return your MockThirdPartyComponent object rather than the real implementation of the ThirdPartyComponentInterface. By using aspects rather than manual code changes, your component is prepared for isolation unit testing automatically after the aspect is woven into your application.

Similar to Recipe 6.3, this recipe shows another typical application of the Cuckoo's Egg aspect-oriented design pattern, which is discussed in Recipe 23.1.


You can switch between using the real or mock implementation of the external component at compile time by creating two different AspectJ build configuration files. A build configuration file that includes the mock object testing aspect will result in a build that will be all set for isolation unit testing of your component, and a build configuration file that excludes the mock object testing aspect will result in a deployment build that uses the real implementation of the external component.

AspectJ makes the task of applying mock objects for testing purposes less intrusive to your code, much more scalable and much less of a headache when it comes to switching between testing and deployment builds.

See Also

Using different build configurations to vary the aspects applied to an application is explained in Recipe 2.5; the call(Signature) pointcut is described in Recipe 4.1; the around( ) form of advice is covered in Recipe Recipe 13.4; how to implement the Abstract Factory and Factory Method design patterns using AspectJ is shown in Recipes Recipe 17.3 and Recipe 17.4, respectively; the Cuckoo's Egg aspect-oriented design pattern is discussed in Recipe 23.1.



AspectJ Cookbook
Aspectj Cookbook
ISBN: 0596006543
EAN: 2147483647
Year: 2006
Pages: 203
Authors: Russ Miles

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net