mocking Logger class 方法解决这个错误org/apache/commons/logging/LogFactory java.lang.NoClassDefFoundError?

Is mocking of Logger class method solution of this error org/apache/commons/logging/LogFactory java.lang.NoClassDefFoundError?

我已经为我使用 Apache 记录器的 class 编写了一个测试,所以我创建了一个如下所述的自定义记录器。

 import java.io.Externalizable;
    import java.io.IOException;
    import java.io.ObjectInput;
    import java.io.ObjectOutput;
    import java.io.Serializable;

    import java.sql.SQLException;

    import com.medicalis.platform.InternalPlatformException;
    import com.medicalis.platform.MedicalisException;
    import com.medicalis.platform.MedicalisSQLException;
    import com.medicalis.platform.logging.windows.LogSeverity;
    import com.medicalis.platform.logging.windows.WindowsLoggingUtils;

    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;

    public class Logger implements Serializable, Externalizable {
       private Log myLogger = null;
       private String categoryName = null;
       private static final int DEFAULT_EVENT_ID = 9;
       private static final String SQL_STATE_CONSTRAINT_ERROR = "23000";

       /**
        * Creates a new Logger object.
        */
       public Logger() {
       }

       /**
        * Write objects member variables to object stream
        *
        * @param out - ObjectOutput
        *
        * @throws IOException
        */
       public void writeExternal(ObjectOutput out)
          throws IOException {
          out.writeObject(categoryName);
       }

       /**
        * Reads object stream to reconstruct object's member variables
        *
        * @param in - ObjectInput
        *
        * @throws IOException
        * @throws ClassNotFoundException
        */
       public void readExternal(ObjectInput in)
          throws IOException, ClassNotFoundException {
          this.categoryName = (String) in.readObject();
          this.myLogger = LogFactory.getLog(this.categoryName);
       }

       /**
        * Initializes log factory used for all logging.
        *
        * @param log - base logger
        * @param categoryName - String name of logger category
        */
       private Logger(Log log, String categoryName) {
          this.categoryName = categoryName;
          myLogger = log;
       }

       /**
        * Returns a Category instance for the given category name.
        *
        * @param categoryName - String name to use as the name for the log category
        *
        * @return Category logger object
        */
       public static Logger getInstance(final String categoryName) {
          Log log = LogFactory.getLog(categoryName);
          Logger logger = new Logger(log, categoryName);

          return logger;
       }

       /**
        * Returns a Category instance for the given category name.
        *
        * @param categoryClass - Object whose fully qualified classname will be used to determine the
        *        log category
        *
        * @return Category logger object
        */
       public static Logger getInstance(final Class categoryClass) {
          return getInstance(categoryClass.getName().toString());
       }

       /**
        * Logs a message at trace priority.
        *
        * @param obj object whose toString() method is used to get the message to log
        */
       public void trace(Object obj) {
          this.trace((obj == null) ? "null" : obj.toString(), null);
       }

       /**
        * Logs a message at debug priority.
        *
        * @param msg message to log
        * @param t object describing the exception or error that needs to be logged
        */
       public void trace(String msg, Throwable t) {
          myLogger.trace(msg, t);
       }

       /**
        * Logs a message at debug priority.
        *
        * @param obj object whose toString() method is used to get the message to log
        */
       public void debug(Object obj) {
          this.debug((obj == null) ? "null" : obj.toString(), null);
       }

       /**
        * Logs a message at debug priority.
        *
        * @param msg message to log
        * @param t object describing the exception or error that needs to be logged
        */
       public void debug(String msg, Throwable t) {
          myLogger.debug(msg, t);
       }

       /**
        * Logs a message at info priority.
        *
        * @param obj object whose toString() method is used to get the message to log
        */
       public void info(Object obj) {
          this.info((obj == null) ? "null" : obj.toString(), null);
       }

       /**
        * Logs a message at info priority.
        *
        * @param msg message to log
        * @param t object describing the exception or error that needs to be logged
        */
       public void info(String msg, Throwable t) {
          myLogger.info(msg, t);
       }

       /**
        * Logs a message at warn priority.
        *
        * @param obj object whose toString() method is used to get the message to log
        */
       public void warn(Object obj) {
          this.warn((obj == null) ? "null" : obj.toString(), null);
       }

       /**
        * Logs a message at warn priority.
        *
        * @param msg message to log
        * @param t object describing the exception or error that needs to be logged
        */
       public void warn(String msg, Throwable t) {
          myLogger.warn(msg, t);

          if (myLogger.isWarnEnabled()) {
             if (t instanceof MedicalisException) {
                WindowsLoggingUtils.log((MedicalisException) t, LogSeverity.WARNING);
             } else if (t == null) {
                WindowsLoggingUtils.log(msg, DEFAULT_EVENT_ID, LogSeverity.WARNING);
             }
          }
       }

       /**
        * Logs a message at error priority.
        *
        * @param obj object whose toString() method is used to get the message to log
        */
       public void error(Object obj) {
          this.error((obj == null) ? "null" : obj.toString(), null);
       }

       /**
        * Logs a message at error priority.
        *
        * @param msg message to log
        * @param t object describing the exception or error that needs to be logged
        */
       public void error(String msg, Throwable t) {
          myLogger.error(msg, t);

          if (myLogger.isErrorEnabled() && t instanceof MedicalisException) {
             if (!isSQLConstraintError(t)) {
                WindowsLoggingUtils.log((MedicalisException) t, LogSeverity.ERROR);
             }
          }
       }

       /**
        * Returns whether or not trace logging is enabled.
        *
        * @return boolean indicating if trace logging is enabled (true).
        */
       public boolean isTraceEnabled() {
          return myLogger.isTraceEnabled();
       }

       /**
        * Returns whether or not debug logging is enabled.
        *
        * @return boolean indicating if debug logging is enabled (true).
        */
       public boolean isDebugEnabled() {
          boolean isDebug = false;

          isDebug = myLogger.isDebugEnabled();

          if (!isDebug) {
             isDebug = Boolean.getBoolean("system.debug.on");
          }

          return isDebug;
       }

       /**
        * Returns whether or not info logging is enabled.
        *
        * @return boolean indicating if info logging is enabled (true).
        */
       public boolean isInfoEnabled() {
          return myLogger.isInfoEnabled();
       }

       /**
        * Returns whether or not warn logging is enabled.
        *
        * @return boolean indicating if warn logging is enabled (true).
        */
       public boolean isWarnEnabled() {
          return myLogger.isWarnEnabled();
       }

       /**
        * Returns whether or not error logging is enabled.
        *
        * @return boolean indicating if error logging is enabled (true).
        */
       public boolean isErrorEnabled() {
          return myLogger.isErrorEnabled();
       }

       /**
        * Method to determine if the error being thrown is the
        * result of a SQLConstraint Error. (This type of error may be logged
        * differently).
        *
        * @param t Throwable
        *
        * @return boolean
        */
       public boolean isSQLConstraintError(Throwable t) {
          boolean isContraintErr = false;

          Throwable cause = t;

          while (cause != null) {
             if (cause instanceof SQLException) {
                SQLException sqlCause = (SQLException) cause;

                String sqlState = sqlCause.getSQLState();

                if ((sqlState != null) && sqlState.equals(SQL_STATE_CONSTRAINT_ERROR)) {
                   isContraintErr = true;
                }

                break;
             }

             // check for infinite nesting of exceptions
             Throwable lastCause = cause;

             cause = cause.getCause();

             if (cause == lastCause) {
                break;
             }
          }

          return isContraintErr;
       }
    }

我在我的 class 中使用这个记录器,如下所述。

 private static final Logger log = Logger.getInstance(TimeZoneUtil.class);

如果在我的测试版本中我包含如下所述的 commons-apache-logger-dependency,那么我不会收到任何名为 java.lang.NoClassDefFoundError 的错误:org/apache/commons/logging/LogFactory

<fileset dir="./../thirdparty/commons-logging-1.1.1">
         <include name="*.jar" />
      </fileset>

但我不想在我的测试构建中使用这个包含的 jar,所以有任何其他方法可以模拟 getInstance() 方法,所以它不应该进入记录器 class 来创建日志或任何其他摆脱此错误的方法。

目前我正在使用下面提到的 Power Mockito,通过它我可以模拟 Logger class 但即使在模拟它之后我也会收到错误。

      PowerMockito.mockStatic(Logger.class);
      Logger logger = mock(Logger.class);
      when(Logger.getInstance(TimeZoneUtil.class)).thenReturn(logger);
      when(Logger.getInstance(any(String.class))).thenReturn(logger);

错误:

Testsuite: com.medicalis.platform.timing.TimeZoneUtilTest
Tests run: 0, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.033 sec

Testcase: com.medicalis.platform.timing.TimeZoneUtilTest took 0 sec
    Caused an ERROR
org/apache/commons/logging/LogFactory
java.lang.NoClassDefFoundError: org/apache/commons/logging/LogFactory
    at com.medicalis.platform.logging.Logger.getInstance(Logger.java:107)
    at com.medicalis.platform.logging.Logger.getInstance(Logger.java:122)
    at com.medicalis.platform.timing.TimeZoneUtil.<clinit>(TimeZoneUtil.java:30)
    at com.medicalis.platform.timing.KnownTimeZone.loadValuesImpl(KnownTimeZone.java:51)
    at com.medicalis.platform.timing.TimeZoneUtilTest.setUp(TimeZoneUtilTest.java:69)
Caused by: java.lang.ClassNotFoundException: org.apache.commons.logging.LogFactory
    at java.lang.ClassLoader.loadClass(ClassLoader.java:352)

请通过提供一些好的方法帮助我,这样我就不会得到这个错误它可以模拟方法的对象模拟或任何其他可以解决这个问题的方法,除了在测试构建中提供依赖提前谢谢你。

包括以下我尝试过的测试用例的起点 class .

//import static org.easymock.EasyMock.mock;
import static org.junit.Assert.*;

import java.sql.SQLException;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import javax.sql.RowSet;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.json.JSONArray;
import org.json.JSONException;
import org.junit.After;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.powermock.api.mockito.PowerMockito.*;

import static org.mockito.Matchers.*;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
//import static org.easymock.EasyMock.expect;
import com.medicalis.common.shared.UUIDHelper;
import com.medicalis.platform.logging.Logger;
import com.medicalis.platform.user.valueobject.UserData;
//import static org.powermock.api.easymock.PowerMock.mockStatic;

@RunWith(PowerMockRunner.class)
@PrepareForTest({Logger.class, LogFactory.class, TimeZoneUtil.class, KnownTimeZone.class})
public class TimeZoneUtilTest {
   public static final String UNIT_TEST_PROPERTY = "timeZoneUtilsUnitTestRunning";
   private static final String EUCLA_TZ = "Australia/Eucla";
   private static final String LA_TZ = "America/Los_Angeles";
   private static final String PERTH_TZ = "Australia/Perth";
   private static final String AUSTRALIA_PERTH_SHORT_CD = "APER";
   private static final String PERTH_TZ_JSON = "[{'timeZoneCd': 'APER', 'javaId': 'Australia/Perth', 'displayCode': 'AWST', 'timeZoneId': 12, 'windowsId': '(UTC+08:00) Perth'}]";

   private static List javaIdTestValue, timeZonesTestValue;
   private static UserData professionalUserData, rmUserData;

   /*package*/ static boolean isUnitTestRunning() {
      return String.valueOf(true).equals(System.getProperty(TimeZoneUtilTest.UNIT_TEST_PROPERTY));
   }

   @BeforeClass
   public static void setUp() throws SQLException, JSONException {

     /* mockStatic(LogFactory.class);
      Log log = mock(Log.class);
      when(LogFactory.getLog(anyString())).thenReturn(log);
   */

     mockStatic(Logger.class);
      Logger logger = mock(Logger.class);
     // expect(Logger.getInstance(TimeZoneUtil.class)).andReturn(logger);

     when(Logger.getInstance(TimeZoneUtil.class)).thenReturn(logger);
      when(Logger.getInstance(any(String.class))).thenReturn(logger);

      when(logger.isDebugEnabled()).thenReturn(Boolean.valueOf("true"));

      doNothing().when(logger).error(anyString(),any());
      doNothing().when(logger).warn(anyString(),any());

      doNothing().when(logger).debug(anyString(),any());

     doNothing().when(logger).error(anyString(),any());
      System.setProperty(UNIT_TEST_PROPERTY, String.valueOf(true));

      RowSet rowSet = new TimeZoneUtilTestRowSet();
      rowSet.next();
      rowSet.setString("javaId", EUCLA_TZ);
      javaIdTestValue = Collections.singletonList(rowSet);

      RowSet timeZoneRowSet = new TimeZoneUtilTestRowSet(new JSONArray(PERTH_TZ_JSON));
      timeZonesTestValue = Collections.singletonList(timeZoneRowSet);
      KnownTimeZone.loadValuesImpl(timeZonesTestValue);

      professionalUserData = new UserData() {
         @Override
         public boolean isProfessionalContext() {
            return true;
         }

         @Override
         public String getProfessionalTimeZoneId() {
            return LA_TZ;
         }
      };
      professionalUserData.setPreferredTimeZone(AUSTRALIA_PERTH_SHORT_CD);

      rmUserData = new UserData();
      rmUserData.setPreferredTimeZone(AUSTRALIA_PERTH_SHORT_CD);
   }

在尝试模拟静态方法之前:

   @Before
   public void before() throws Exception {
      mockStatic(LogFactory.class);
      Log log = mock(Log.class);
      when(LogFactory.getLog(anyString())).thenReturn(log);
      when(LogFactory.getLog(any(Class.class))).thenReturn(log);
   }

,或

   @Before
   public void before() throws Exception {
     mockStatic(Logger.class);
     Logger logger = mock(Logger.class);
     when(Logger.getInstance(any())).thenReturn(logger);
   }

你可以这样写你的测试...

public class TimeZoneUtilTest {
   public static final String UNIT_TEST_PROPERTY = "timeZoneUtilsUnitTestRunning";
   private static final String EUCLA_TZ = "Australia/Eucla";
   private static final String LA_TZ = "America/Los_Angeles";
   private static final String PERTH_TZ = "Australia/Perth";
   private static final String AUSTRALIA_PERTH_SHORT_CD = "APER";
   private static final String PERTH_TZ_JSON = "[{'timeZoneCd': 'APER', 'javaId': 'Australia/Perth', 'displayCode': 'AWST', 'timeZoneId': 12, 'windowsId': '(UTC+08:00) Perth'}]";

   private static List javaIdTestValue, timeZonesTestValue;
   private static UserData professionalUserData, rmUserData;

    @Mock
    Logger logger;
    //....
   }