Skip to main content

Make Our Code More Testable with Proxy Design Pattern


If you heard about the term separation of concerns, you could agree this concept is very important for making a system "clean". One of the most important characteristics of a clean system is testable.

Let me tell you my story about how I've just come acrosss the design pattern Proxy.

Note: to get started, you can find a very simple example of the pattern Proxy here

Let's start!

I have an interface as below:
public interface DocumentGenerator {
    File generate(Document document) throws BusinessException;
}

The following is my first implementation for a concrete class of DocumentGenerator.
public class DocumentGeneratorImpl implements DocumentGenerator {
 private Dossier dossier;
 private Locale locale;
 
 public DocumentGeneratorImpl(Dossier dossier, Locale locale) {
  validateNotNullParams(dossier, locale);
  this.dossier = dossier;
  this.locale = locale;
 }
 
 private void validateNotNullParams(LibertyDossier dossier, Locale locale) {
  if(Objects.isNull(dossier)) {
   throw new IllegalArgumentException("Dossier must not be null!");
  }
  
  if(Objects.isNull(dossier.getDossierType())){
   throw new IllegalArgumentException("Dossier type must not be null!");
  }
  
  if(Objects.isNull(locale)) {
   throw new IllegalArgumentException("Locale must be defined!");
  }
 }
 
 @Override
 public File generate(Document document, boolean temporary) 
  throws BusinessException {
  setCobIdForDossierIfNotExist();
  switch (dossier.getDossierType()) {
  case TYPE_A:
   // Do some logic here in case TYPE_A
  case TYPE_BA:
   // Do some logic here in case TYPE_B
  default:
   throw new BusinessException("Not supported dossier type");
  }
 }
 
 private void setCobIdForDossierIfNotExist() {
  if(StringUtils.isEmpty(dossier.getCobId())) {
   String generatedCobId = DossierService.getInstance().generateCobId();
   dossier.setCobId(generatedCobId);
  }
 }
}

The client code constructs the concrete class DocumentGeneratorImpl.
DocumentGenerator  generator = new DocumentGeneratorImpl(dossier, locale);

I saw that some private methods "validateNotNullParams(dossier, locale)" and "setCobIdForDossierIfNotExist()" are just a second responsibility of class DocumentGeneratorImpl.

Follow "S" of SOLID  - Single Responsibility

Firstly, I was thinking about how to separate these methods into another class. Then, I create a class called DocumentGeneratorHelper which contains to these methods. It was just improved a bit.

public class DocumentGeneratorImpl implements DocumentGenerator {
 private Dossier dossier;
 private Locale locale;
 
 public DocumentGeneratorImpl(Dossier dossier, Locale locale) {
  DocumentGeneratorHelper.validateNotNullParams(dossier, locale);
  this.dossier = dossier;
  this.locale = locale;
 }
 
 @Override
 public File generate(Document document, boolean temporary) 
   throws BusinessException {
  DocumentGeneratorHelper.setCobIdForDossierIfNotExist();
  switch (dossier.getDossierType()) {
  case TYPE_A:
   // Do some logic here in case TYPE_A
  case TYPE_BA:
   // Do some logic here in case TYPE_B
  default:
   throw new BusinessException("Not supported dossier type");
  }
 }
 
}

However, if I create tests for DocumentGeneratorImpl, I need to mock DocumentGeneratorHelper. Huh....! Any better approach?

I was thinking about that it is somehow I need to apply an Aspect-Oriented Programming (AOP) approach. As my google searching result, there are some approaches but they are quite complicated and heavy. They use Java Proxy or AOP Frameworks such as AspectJ.

A Simple AOP Approach

Fortunately, the keyword "proxy" leads me to the pattern Proxy. The idea of Proxy is really simple and cool!



Okay, now I create a Proxy instead of a Helper as before.

public class DocumentGeneratorProxy implements DocumentGenerator{
 private DocumentGenerator generator;
 private Dossier dossier;
 
 public DocumentGeneratorProxy(Dossier dossier, Locale locale) {
  validateNotNullParams(dossier, locale);
  this.dossier = dossier;
  this.generator = new DocumentGenerator(this.dossier, locale);
 }

 private void validateNotNullParams(Dossier dossier, Locale locale) {
  if(Objects.isNull(dossier)) {
   throw new IllegalArgumentException("Dossier must not be null!");
  }
  
  if(Objects.isNull(dossier.getDossierType())){
   throw new IllegalArgumentException("Dossier type must not be null!");
  }
  
  if(Objects.isNull(locale)) {
   throw new IllegalArgumentException("Locale must be defined!");
  }
 }

 @Override
 public File generate(Document document, boolean temporary)
   throws BusinessException {
  setCobIdForDossierIfNotExist();
  return generator.generate(document, temporary);
 }

 private void setCobIdForDossierIfNotExist() {
  if(StringUtils.isEmpty(dossier.getCobId())) {
   String generatedCobId = DossierService.getInstance().generateCobId();
   dossier.setCobId(generatedCobId);
  }
 }

}

Done! DocumentGeneratorImpl  doesn't contain its second responsibilities anymore. These methods are tested by the Proxy and we don't need to mock in DocumentGeneratorImpl.
the public class DocumentGeneratorImpl implements DocumentGenerator {
 private Dossier dossier;
 private Locale locale;
 
 public LibertyDocumentGenerator(Dossier dossier, Locale locale) {
  this.dossier = dossier;
  this.locale = locale;
 }
 
 @Override
 public File generate(Document document, boolean temporary) 
   throws BusinessException {
  switch (dossier.getDossierType()) {
  case TYPE_A:
   // Do some logic here in case TYPE_A
  case TYPE_BA:
   // Do some logic here in case TYPE_B
  default:
   throw new BusinessException("Not supported dossier type");
  }
 }
 
}

So, instead of calling directly DocumentGeneratorImpl, we call DocumentGeneratorProxy in the client code as below:

DocumentGenerator  generator = new DocumentGeneratorProxy(dossier, locale);

Happy codings!

Comments

Popular posts from this blog

The HelloWorld example of JSF 2.2 with Myfaces

I just did by myself create a very simple app "HelloWorld" of JSF 2.2 with a concrete implementation Myfaces that we can use it later on for our further JSF trying out. I attached the source code link at the end part. Just follow these steps below: 1. Create a Maven project in Eclipse (Kepler) with a simple Java web application archetype "maven-archetype-webapp". Maven should be the best choice for managing the dependencies , so far. JSF is a web framework that is the reason why I chose the mentioned archetype for my example. 2. Import dependencies for JSF implementation - Myfaces (v2.2.10) into file pom.xml . The following code that is easy to find from  http://mvnrepository.com/  with key words "myfaces". <dependency> <groupId>org.apache.myfaces.core</groupId> <artifactId>myfaces-api</artifactId> <version>2.2.10</version> </dependency> <dependency> <groupId>org.apache.myfaces.core<...

Junit - Test fails on French or German string assertion

In my previous post about building a regex to check a text without special characters but allow German and French . I met a problem that the unit test works fine on my machine using Eclipse, but it was fail when running on Jenkins' build job. Here is my test: @Test public void shouldAllowFrenchAndGermanCharacters(){ String source = "ÄäÖöÜüß áÁàÀâÂéÉèÈêÊîÎçÇ"; assertFalse(SpecialCharactersUtils.isExistSpecialCharater(source)); } Production code: public static boolean isExistNotAllowedCharacters(String source){ Pattern regex = Pattern.compile("^[a-zA-Z_0-9_ÄäÖöÜüß áÁàÀâÂéÉèÈêÊîÎçÇ]*$"); Matcher matcher = regex.matcher(source); return !matcher.matches(); } The result likes the following: Failed tests: SpecialCharactersUtilsTest.shouldAllowFrenchAndGermanCharacters:32 null A guy from stackoverflow.com says: "This is probably due to the default encoding used for your Java source files. The ö in the string literal in the J...

How to apply Lean - Kanban for your business

This is the topic of Scrum Breakfast meetup this time, speaker: Ms. Phuong Bui - Technical Project Manager of YOOSE Pte. Ltd. http://www.meetup.com/Scrum-Breakfast-Vietnam-Agile-and-Scrum-Meetup/events/230313727/ Lean comes from Lean manufacturing is a method that focuses on elimination of wastes. In other words, this is a set of principles for archiving the quality, speed and customer alignment. The first time I knew about the term "Lean" is  from the book Software Craftsmanship . Sandro recommends if we want to transform our pet projects into a real business, we should get familiar with Lean Startup concepts. In this talk, Ms. Phuong pointed out some major wastes includes information (ex: unclear requirements), processes (ex: waiting), physical environment and people. Knowing what the problems should be the best way to eliminate them. The difference between  Single item flow and Batch processing is the second main point; and it is the Lean's idea. Batch pr...

Why Business Rules Engine Matter

"A good DSL minimizes the 'communication gap' between a domain concept and the code that implements it" - Robert C. Martin A Use Case It looks like adopting a new technology is always driven by a business need. The organizations, such as banks, have their own business processes (such as data gathering and document management). These processes are different from others but usually the differences are not big. How do we build a product which can be reused the similar features but still be adaptable with the specific requirements of each bank? Then, the answer is we should have a library/framework. There is an idea that we can implement this library/framework by using a business rules engine. Which has the following benefits: Be able to modify implementation of business domain at runtime (such as XML, CSV) S ource code is more readable for both developers and domain experts. Therefore, source code can be consider it as a document Since Business Rules Engine...

Java 8 - Persistent data structure

The following is a series of posts about "functional programming in Java" which is the result of my understanding by reading the book " Java 8 in Action: Lambdas, Streams, and Functional-style Programming, by Alan Mycroft and Mario Fusco ". 1. Why functional programming? 2. Functional programming in Java 8 3. Java 8 - Using Functions as Values 4. Java 8 - Persistent data structure Persistent data structure is also known as a simple technique but it's very important. Its other names are functional data structure and immutable data structure. Why is it "persistent"? Their values persist and are isolated from changes happening elsewhere. That's it! This technique is described as below: If you need a data structure to represent the result of a computation, you should make a new one and not mutable an existing data structure. Destructive updates version public static A doSomething(A a){ a.setProp1("new value"); return...