Matthieu Vergne's Homepage

Last update: 25/08/2019 20:05:36

Proxy, Decorator, Adapter, Façade, ...
How to Test a Wrapper?

Context

A wrapper contains another instance, the delegate, to which it transfers any call it receives. The calls of the user to a wrapper are transferred to the delegate, and the answers of the delegate are transferred back by the wrapper to the user. The whole point of a wrapper is, in other words, to play as an intermediary between the user and the delegate.

Different types of wrappers can be designed:

Although the term of wrapper have been initially used only for the adapter and the decorator patterns, more specific terms exist to do the difference. Their common aspect, i.e. the delegate, becomes the main aspect of the more generic concept of wrapper. Since this aspect applies also to other designs, as shown above, we use the term of wrapper more broadly in this article. The examples above are not the only ones, and a single class can combine several of them depending on the needs. Generally speaking, any class that relies on a delegate to provide the main feature will be considered as a wrapper class in this article.

Question: How to Test it?

The wrapper, because it relies on its delegate to provide the main feature, should preserve its behaviour. But different delegates my come with different behaviours, so should we test the wrapper with any possible delegate? Such an objective would be unreasonable: there may have to much possibilites to properly test all the relevant behaviours.

Some of them implement a single interface, so we may at least test the common behaviours. But since the wrapper itself does not store the logic, it would need to be tested with a specific instance. Such a stratgy would also be unreasonable: we would confirm the wrapper only for a single delegate. Morover, the delegate may require a huge set up effort and, since it may be already tested alone, it seems unreasonable to spend all this effort again to test the wrapper.

Solution: Focus on Data Transfer

In fact, the solution is rather trivial and systematic. Indeed, the contract of a wrapper is not to provide the features of the delegate, but to wrap it. This delegate is the one holding the contract of the feature to implement. The only contract of the wrapper is then to properly play its intermediary role. In other words, it should be tested on its ability to transfer the calls to the delegate, and transfer back the answer of this delegate.

Let's take this interface as an example:


	interface Foo {
		boolean doSomething(String arg1, int arg2);
	}

And this wrapper implementation:


	class FooWrapper {
		private final Foo delegate;
		private final int hiddenArg = 0;

		public FooWrapper(Foo foo) {
			this.delegate = foo;
		}

		public boolean doSomething(String arg) {
			return delegate.doSomething(arg, hiddenArg);
		}
	}

A proper test would check that, for each method:


	public class FooWrapperTest {
		@Test
		public void testDoSomethingIsCorrectlyMapped() {
			// Decide the expected values
			String expectedShownArg = "test";
			int expectedHiddenArg = 0;
			boolean expectedResult = true;
			
			// Store the transferred values
			String[] actualShownArg = {null};
			Integer[] actualHiddenArg = {null};
			FooWrapper wrapper = new FooWrapper(new Foo() {
				public boolean doSomething(String arg1, int arg2) {
					actualShownArg[0] = arg1;
					actualHiddenArg[0] = arg2;
					return expectedResult;
				}
			});
			boolean actualResult = wrapper.doSomething(expectedShownArg);
			
			// Check they correspond
			assertEquals(expectedShownArg, actualShownArg[0]);
			assertEquals(expectedHiddenArg, actualHiddenArg[0], 0);
			assertEquals(expectedResult, actualResult);
		}
	}

The key point is to check that everything is properly transferred. Be careful if you prefer to write the arguments assertions in the delegate method. If it is not called, and you have no returned value to check, you may have a passing test without calling the delegate:


	class FooWrapper {
		private final Foo delegate;
		private final int hiddenArg = 0;

		public FooWrapper(Foo foo) {
			this.delegate = foo;
		}

		public void doSomething(String arg) {
			// Forget to call the delegate
		}
	}

	public class FooWrapperTest {
		@Test
		public void testDoSomethingIsCorrectlyMapped() {
			String expectedShownArg = "test";
			int expectedHiddenArg = 0;
			
			FooWrapper wrapper = new FooWrapper(new Foo() {
				public void doSomething(String arg1, int arg2) {
					// Never called, so no assertion fail and all is green
					assertEquals(expectedShownArg, arg1);
					assertEquals(expectedHiddenArg, arg2, 0);
				}
			});
			wrapper.doSomething(expectedShownArg);
		}
	}

Prefer to systematically check the method is called in this case:


	public class FooWrapperTest {
		@Test
		public void testDoSomethingIsCorrectlyMapped() {
			String expectedShownArg = "test";
			int expectedHiddenArg = 0;
			
			boolean[] isCalled = {false};
			FooWrapper wrapper = new FooWrapper(new Foo() {
				public void doSomething(String arg1, int arg2) {
					assertEquals(expectedShownArg, arg1);
					assertEquals(expectedHiddenArg, arg2, 0);
					
					isCalled[0] = true;
				}
			});
			wrapper.doSomething(expectedShownArg);
			
			assertTrue(isCalled[0]); // Fail here
		}
	}