Tuesday, December 15, 2015

java class mocking dengan mockito

unit test sebaiknya dilakukan dengan menghilangkan dependency terhadap sistem lain. lalu bagaimana jika di dalam class/method yang hendak kita test mengandung komunikasi dengan sistem lain misalnya menembak API dari sistem lain?

kita bisa memalsukan method dari service yang menembak ke API sistem lain tersebut, sehingga seolah-olah sistem kita telah merequest dan menerima response. atau dengan kata lain, kita me-mock service tersebut, dan menentukan sendiri (berdasarkan asumsi dan/atau kondisi yang diinginkan dalam unit test tersebut) response yang diharapkan dari method tersebut.

mari kita coba dengan menggunakan library mockito. aplikasi menggunakan framework spring, unit test dengan junit.

misalkan kita punya class yang akan kita test:
 @Service  
 public class UseMeForTesting {  
        
      @Autowired  
      BidEngineRemoteService bidEngineRemoteService;  
        
      public String someTransaction() throws BidException{  
             
           String refNo = null;  
             
           BidEngineResponse response = bidEngineRemoteService.apiGet("/api/test");  
           if(response == null){  
                throw new BidException("no response.");  
           }else if(response.isError || response.getJsonResponse() == null){  
                throw new BidException("error response.");  
           }else if(response.getJsonResponse() != null){  
                refNo = response.getJsonResponse().get("refNo").toString();  
                refNo = refNo.replace("\"", "");  
           }  
             
           return refNo;  
             
      }  
        
      /**  
       * for mock injection purpose  
       * @param bidEngineRemoteService  
       */  
      public void setBidEngineRemoteService(BidEngineRemoteService bidEngineRemoteService){  
           this.bidEngineRemoteService = bidEngineRemoteService;  
      }  
        
 }  

di dalamnya memanggil service yang menembak api dari service lain.
 @Service  
 public class BidEngineRemoteService {  
   
   private static final Logger LOGGER = LoggerFactory.getLogger(BidEngineRemoteService.class);  
   
   @Autowired  
   private HttpRequest httpRequest;  
   
   @Value(value = "${bid.host}")  
   private String bidHost;  
   
   private final int MAX_REQUEST = 3;  
   
   public BidEngineResponse apiGet(String endpoint) {  
     return executeWithRetry(new ApiRequest() {  
       @Override  
       protected HttpRequest.HttpRequestResult doRequest() throws IOException {  
         return httpRequest.doGetRequestAndGetResult(createUrl(endpoint));  
       }  
     });  
   }  
   
   private String createUrl(String endpoint) {  
     if (!endpoint.substring(0, 1).equals("/")) {  
       endpoint = "/" + endpoint;  
     }  
     return bidHost + endpoint;  
   }  
   
   private BidEngineResponse executeWithRetry(ApiRequest apiRequest) {  
     int count = MAX_REQUEST;  
     while (count-- > 0) {  
       if (apiRequest.execute()) {  
         return apiRequest.getResult();  
       }  
     }  
     return null;  
   }  
   
   static abstract class ApiRequest {  
     private BidEngineResponse response;  
   
     protected abstract HttpRequest.HttpRequestResult doRequest() throws IOException;  
   
     public boolean execute() {  
       response = run();  
       return response != null;  
     }  
   
     private BidEngineResponse run() {  
       BidEngineResponse bidEngineResponse = null;  
       HttpRequest.HttpRequestResult result;  
   
       try {  
         result = doRequest();  
         bidEngineResponse = new BidEngineResponse(result);  
   
       } catch (IOException e) {  
         bidEngineResponse = new BidEngineResponse();  
         bidEngineResponse.isError = true;  
   
         if (e instanceof java.net.ConnectException) {  
           bidEngineResponse.setError("Could not connect to bid engine");  
         } else {  
           bidEngineResponse.setError(e.getMessage());  
         }  
   
         e.printStackTrace();  
       }  
   
       return bidEngineResponse;  
     }  
   
     public BidEngineResponse getResult() {  
       return response;  
     }  
   }  
   
   public static class BidEngineResponse extends HttpRequest.HttpRequestResult {  
   
     public static final String FIELD_ERROR = "Error";  
     private JsonObject root = null;  
   
     public BidEngineResponse() {  
       super();  
     }  
   
     public BidEngineResponse(HttpRequest.HttpRequestResult result) {  
       this.response = result.response;  
       this.isError = result.isError;  
   
       JsonElement jsonElement = result.response;  
       if (null != jsonElement && !jsonElement.isJsonNull()) {  
         if (jsonElement.isJsonObject()) {  
           root = jsonElement.getAsJsonObject();  
         } else {  
           root = new JsonObject();  
         }  
       } else {  
         root = new JsonObject();  
       }  
     }  
   
     public JsonObject getJsonResponse() {  
       return root;  
     }  
   
     public void setError(String message) {  
       root = null;  
       root = new JsonObject();  
       root.addProperty(FIELD_ERROR, message);  
     }  
   
     public String getError() {  
       return getValueFromErrorMap(FIELD_ERROR);  
     }  
   
     public String getValueFromErrorMap(String field) {  
       if (isError) {  
         JsonElement jsonElement = root.get(field);  
         if (null != jsonElement) {  
           return jsonElement.getAsString();  
         }  
       }  
       return null;  
     }  
       
     /**  
      * mock injection purpose  
      * @param isError  
      */  
     public void setError(boolean isError){  
          this.isError = isError;  
     }  
       
     /**  
      * mock injection purpose  
      * @param root  
      */  
     public void setRoot(JsonObject root){  
          this.root = root;  
     }  
   
   }  
        
 }  

untuk menghindari dependency, maka kita bisa membuat 'mock' untuk service di atas dengan menggunakan asumsi response yang diharapkan dari service tersebut. berikut contoh implementasi mocking dengan mockito pada jUnit test class.

 @RunWith(SpringJUnit4ClassRunner.class)  
 @ContextConfiguration(locations = {"/spring/applicationContext.xml"})  
 public class UseMeForTestingTest {  
        
      @Mock  
      BidEngineRemoteService bidEngineRemoteService;  
        
      @Autowired  
      UseMeForTesting useMeForTesting;  
        
      @Before  
      public void setUp() throws Exception {  
           MockitoAnnotations.initMocks(this);  
      }  
        
      private static final String REFNO = "test123";  
        
      @Test  
      public void someTransactionTest(){  
             
           JsonObject obj = new JsonObject();  
           obj.addProperty("refNo", REFNO);  
           obj.addProperty("message", "pesan");  
             
           BidEngineRemoteService.BidEngineResponse bidResponse = new BidEngineRemoteService.BidEngineResponse();  
           bidResponse.setError(false);  
           bidResponse.setRoot(obj);  
             
           Mockito.doReturn(bidResponse)  
                .when(bidEngineRemoteService)  
                .apiGet("/api/test");  
             
           useMeForTesting.setBidEngineRemoteService(bidEngineRemoteService);  
             
           try {  
                String result = useMeForTesting.someTransaction();  
                Assert.assertTrue(REFNO.equals(result));  
           } catch (BidException e) {  
                Assert.assertTrue(false);  
           }  
             
      }  
        
 }  

pada contoh di atas, kita mengasumsikan bidEngineRemoteService.apiGet("/api/test") akan meresponse dengan root object obj dan error=false. sehingga method UseMeForTesting.someTransaction() akan memberikan hasil refNo, sesuai dengan mock yang kita set.

silakan lihat http://docs.mockito.googlecode.com/hg/org/mockito/Mockito.html untuk contoh-contoh lainnya.

No comments:

Post a Comment