简体   繁体   中英

Why do my Mockito-inline JUnit tests crash when run through Ant, and succeed in IntelliJ IDEA?

I've started using Mockito to mock some classes in my unit tests, for various reasons, and as I was developing, I got everything to work. Once I finished my changes, I pushed to our build server, and discovered that the unit tests are broken when run through Ant. I've been able to reproduce this in a simple stripped-down case, in which I'm using Mockito to test the mock the static method of a dependency of the class under test. Below I've pasted the source files, the test file, the Ant script, and the Ivy dependencies file describing the versions of each library I'm using (all of them up to date at this time), followed by the verbose error I'm seeing.

But if I create these several files in a directory (with the two classes under src/com/example and the unit test under tests/com/example and open it up as an IntelliJ IDEA project, using all the same dependencies (including JUnit) and run all tests, it succeeds. The only change from the standard module template is specifying the JDK path and Java level to 1.8. That's part of what's making it so puzzling. For what it's worth, Ant acts the same whether invoked from within IntelliJ IDEA's built-in Ant integration, or separately on the command line.

BusinessClass.java

package com.example;

public class BusinessClass {
    public String returnString() {
        StaticDependency.DoSomething();
        return "Answer";
    }
}

StaticDependency.java

package com.example;

public class StaticDependency {
    public static void DoSomething() {
        throw new RuntimeException("Real code causes side effects I'm isolating from");
    }
}

BusinessClassTest.java

package com.example;

import org.junit.Test;
import org.mockito.MockedStatic;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mockStatic;

public class BusinessClassTest {
    @Test
    public void returnString() {
        final BusinessClass classUnderTest = new BusinessClass();

        try (final MockedStatic<StaticDependency> ignored = mockStatic(StaticDependency.class)) {
            assertEquals("Answer", classUnderTest.returnString());
        }
    }
}

ivy.xml

<ivy-module version="2.0">
  <info organisation="com.example" module="MockitoTest"/>
  <dependencies>
    <dependency org="junit" name="junit" rev="4.13.2"/>

    <!-- Mockito and dependencies -->
    <dependency org="org.mockito" name="mockito-inline" rev="4.2.0"/>
    <dependency org="net.bytebuddy" name="byte-buddy-dep" rev="1.12.6"/>
    <dependency org="org.objenesis" name="objenesis" rev="3.2"/>
  </dependencies>
</ivy-module>

build.xml

<project default="all" xmlns:ivy="antlib:org.apache.ivy.ant">

  <property name="lib" location="Lib"/>
  <property name="src" location="src"/>
  <property name="tests" location="tests"/>
  <property name="build-test" location="build-test"/>
  <property name="build" location="build"/>

  <path id="build.classpath">
    <fileset dir="${lib}">
      <include name="**/*.jar"/>
    </fileset>
  </path>

  <target name="all" depends="clean, resolve, compile, test"/>

  <target name="clean">
    <delete dir="${build}"/>
    <delete dir="${build-test}"/>
    <delete dir="${lib}"/>
  </target>

  <target name="resolve" description="Download dependencies">
    <ivy:resolve file="ivy.xml"/>
    <mkdir dir="${lib}"/>
    <ivy:retrieve pattern="${lib}/[artifact]-[type].[ext]"/>
  </target>

  <target name="compile" description="Compile the source code">
    <mkdir dir="${build}"/>
    <javac destdir="${build}">
      <src path="${src}"/>
      <classpath refid="build.classpath"/>
    </javac>

    <mkdir dir="${build-test}"/>
    <javac destdir="${build-test}">
      <src path="${src}"/>
      <src path="${tests}"/>
      <classpath refid="build.classpath"/>
    </javac>
  </target>

  <target name="test" depends="compile" description="Run all unit tests">
    <junit>
      <classpath>
        <path refid="build.classpath"/>
        <path path="${build-test}"/>
      </classpath>
      <formatter type="plain" usefile="false"/>

      <test name="com.example.BusinessClassTest" />
    </junit>
  </target>
</project>

JUnit output

Testsuite: com.example.BusinessClassTest
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.889 sec

Testcase: returnString took 0.779 sec
    Caused an ERROR
Could not modify all classes [class com.example.StaticDependency]
org.mockito.exceptions.base.MockitoException: Could not modify all classes [class com.example.StaticDependency]
    at com.example.BusinessClassTest.returnString(Unknown Source)
Caused by: java.lang.IllegalStateException: 
Byte Buddy could not instrument all classes within the mock's type hierarchy

This problem should never occur for javac-compiled classes. This problem has been observed for classes that are:
 - Compiled by older versions of scalac
 - Classes that are part of the Android distribution
    at org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator.triggerRetransformation(InlineBytecodeGenerator.java:280)
    at org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator.mockClassStatic(InlineBytecodeGenerator.java:221)
    at org.mockito.internal.creation.bytebuddy.TypeCachingBytecodeGenerator.mockClassStatic(TypeCachingBytecodeGenerator.java:63)
    at org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker.createStaticMock(InlineDelegateByteBuddyMockMaker.java:560)
    at org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker.createStaticMock(InlineByteBuddyMockMaker.java:83)
    at org.mockito.internal.util.MockUtil.createStaticMock(MockUtil.java:147)
    at org.mockito.internal.MockitoCore.mockStatic(MockitoCore.java:142)
    at org.mockito.Mockito.mockStatic(Mockito.java:2164)
    at org.mockito.Mockito.mockStatic(Mockito.java:2101)
Caused by: java.lang.AbstractMethodError: org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator$ParameterWritingVisitorWrapper.wrap(Lnet/bytebuddy/description/type/TypeDescription;Lorg/objectweb/asm/ClassVisitor;Lnet/bytebuddy/implementation/Implementation$Context;Lnet/bytebuddy/pool/TypePool;Lnet/bytebuddy/description/field/FieldList;Lnet/bytebuddy/description/method/MethodList;II)Lorg/objectweb/asm/ClassVisitor;
    at net.bytebuddy.asm.AsmVisitorWrapper$Compound.wrap(AsmVisitorWrapper.java:746)
    at net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining$WithFullProcessing$RedefinitionClassVisitor.visit(TypeWriter.java:4906)
    at org.objectweb.asm.ClassReader.accept(ClassReader.java:569)
    at org.objectweb.asm.ClassReader.accept(ClassReader.java:424)
    at net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining.create(TypeWriter.java:3951)
    at net.bytebuddy.dynamic.scaffold.TypeWriter$Default.make(TypeWriter.java:2213)
    at net.bytebuddy.dynamic.scaffold.inline.RedefinitionDynamicTypeBuilder.make(RedefinitionDynamicTypeBuilder.java:224)
    at net.bytebuddy.dynamic.scaffold.inline.AbstractInliningDynamicTypeBuilder.make(AbstractInliningDynamicTypeBuilder.java:123)
    at net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase.make(DynamicType.java:3661)
    at org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator.transform(InlineBytecodeGenerator.java:394)
    at sun.instrument.TransformerManager.transform(TransformerManager.java:188)
    at sun.instrument.InstrumentationImpl.transform(InstrumentationImpl.java:428)
    at sun.instrument.InstrumentationImpl.retransformClasses0(Native Method)
    at sun.instrument.InstrumentationImpl.retransformClasses(InstrumentationImpl.java:144)
    at org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator.triggerRetransformation(InlineBytecodeGenerator.java:276)

It turns out my problem was the counterproductive byte-buddy-dep dependency in my ivy.xml file. As @temp-droid pointed out in a GitHub issue I raised on the Mockito project posing this same problem, replacing that with byte-buddy and byte-buddy-agent would work instead.

I then realized that Ivy doesn't require me to explicitly list sub-dependencies (I wasn't aware it knew how to resolve them), and so I was able to update to this much simpler ivy.xml , which works in Ant and IntelliJ alike:

<ivy-module version="2.0">
  <info organisation="com.example" module="MockitoTest"/>
  <dependencies>
    <dependency org="junit" name="junit" rev="4.13.2"/>
    <dependency org="org.mockito" name="mockito-inline" rev="4.2.0"/>
  </dependencies>
</ivy-module>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM