Sekarang Tedi cuba menulis beberapa unit test untuk kod-kod legasi dengan pilihan tiada pilihan untuk menukar kod tersebut kepada kod mesra unit test.

Salah satu kes apabila menulis unit test untuk kod macam ini ialah nak cover bahagian yang ada log siap ada condition lak tu pada logger tersebut! Terpaksalah Tedi mock logger tersebut di mana bukan mudah nak menjayakannya.

  1. Ralat 1: Cannot subclass final class
  2. Ralat 2: Unable to set internal state on a private field

Ralat 1: Cannot subclass final class

Kesimpulannya menurut pemahaman Tedi lah kan ada masalah untuk subclass final class tapi punca sebenarnya tu mungkin disebabkan oleh beberapa level class dalam kod kita. Contohnya macam di bawah nanti Tedi nak mock LoggerFactory tapi ada masalah pada Log4jLoggerAdapter.

Mesej ralat:

java.lang.IllegalArgumentException: Cannot subclass final class class org.slf4j.impl.Log4jLoggerAdapter
at org.mockito.cglib.proxy.Enhancer.generateClass(Enhancer.java:447)
at org.mockito.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25)
at org.mockito.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:217)
at org.mockito.cglib.proxy.Enhancer.createHelper(Enhancer.java:378)
at org.mockito.cglib.proxy.Enhancer.createClass(Enhancer.java:318)
at org.powermock.api.mockito.repackaged.ClassImposterizer.createProxyClass(ClassImposterizer.java:123)
at org.powermock.api.mockito.repackaged.ClassImposterizer.imposterise(ClassImposterizer.java:57)
at org.powermock.api.mockito.internal.mockcreation.MockCreator.createMethodInvocationControl(MockCreator.java:111)
at org.powermock.api.mockito.internal.mockcreation.MockCreator.mock(MockCreator.java:59)
at org.powermock.api.mockito.PowerMockito.spy(PowerMockito.java:220)

Tambah semua annotation ni pada kelas test anda (SLf4J):

@RunWith(PowerMockRunner.class)
@PrepareForTest({LoggerFactory.class, Log4jLoggerAdapter.class})

Kalau guna log4j:

@RunWith(PowerMockRunner.class)
@PrepareForTest({Logger.class})

dan baris kod untuk spy digantikan dengan : loggerSpy = PowerMockito.spy( Logger.getLogger(actionToTest.getClass())); 

Contoh kod unit test dalam dunia sebenar:

*** kod ini adalah cuba jaya namun berjalan setakat ni cuma tak bersih lah contohnya Tedi baru tahu bahawa loggerSpy tu tak perlu mock, cukup declare sebagai private variable sahaja.

 package com.xxx.txn.core.action.validate;  
 import com.xxx.db.model.KodBalasan;  
 import com.xxx.txn.core.Permintaan;  
 import com.xxx.txn.core.Balasan;  
 import org.junit.runner.RunWith;  
 import org.mockito.Mock;  
 import org.powermock.core.classloader.annotations.PrepareForTest;  
 import org.powermock.modules.junit4.PowerMockRunner;  
 import org.slf4j.Logger;  
 import org.junit.Assert;  
 import org.junit.Before;  
 import org.junit.Test;  
 import org.powermock.api.mockito.PowerMockito;  
 import org.powermock.reflect.Whitebox;  
 import org.slf4j.LoggerFactory;  
 import org.slf4j.impl.Log4jLoggerAdapter;  
 import java.util.HashMap;  
 import java.util.Map;  
 import static org.powermock.api.mockito.PowerMockito.when;  
 @RunWith(PowerMockRunner.class)  
 @PrepareForTest({LoggerFactory.class, Log4jLoggerAdapter.class})  
 public class ValidatePrepaidAccountExistenceForAccountingActionTest {  
   private NamaKelasDiuji actionToTest;  
   @Mock  
   private Logger loggerSpy;  
   @Before  
   public void setUp() {  
     actionToTest = new NamaKelasDiuji();  
     loggerSpy = PowerMockito.spy( LoggerFactory.getLogger(actionToTest.getClass()));  
   }  
@Test public void namaUjian() throws Exception { // Setup final Balasan balasan = new Balasan(); final Map<String, Object> serviceMap = new HashMap<>(); Permintaan permintaan = new Permintaan(); balasan.setBalasan(permintaan); // Prepare mock when(loggerSpy.isDebugEnabled()).thenReturn(true); Whitebox.setInternalState(actionToTest.getClass(),"logger", loggerSpy); // Run the test actionToTest.process(balasan, serviceMap); // Verify the results Assert.assertEquals("Should return INVALID_USER_ID", KodBalasan.INVALID_USER_ID,balasan.getKodBalasan()); } }

Ralat 2: Unable to set internal state on a private field

java.lang.RuntimeException: Unable to set internal state on a private field. Please report to mockito mailing list.
at org.mockito.internal.util.reflection.Whitebox.setInternalState(Whitebox.java:29)

....
Caused by: java.lang.RuntimeException: You want me to get this field: 'logger' on this class: 'Object' but this field is not declared withing hierarchy of this class!
at org.mockito.internal.util.reflection.Whitebox.getFieldFromHierarchy(Whitebox.java:40)
at org.mockito.internal.util.reflection.Whitebox.setInternalState(Whitebox.java:25)

PENYELESAIAN:

Import Whitebox daripada PowerMockito bukannya daripada Mockito! Sebab dalam kes ni nak gunakan PowerMock!

//import org.mockito.internal.util.reflection.Whitebox;
import org.powermock.reflect.Whitebox;