jmockit / jmockit1

Advanced Java library for integration testing, mocking, faking, and code coverage
Other
465 stars 240 forks source link

@Capturing not always able to mock objects. #161

Closed brianruss closed 9 years ago

brianruss commented 9 years ago

I have encountered an issue when using @Capturing. It is related to Spring,Apache CXF, the version of JMockit and also the sequence of running tests. The issue is very obscure, but I have a concise set of files available that is a full maven project that demonstrates the problem. I have tried to put these at the end of the issue, but can send these if needed.

The business model is: An interface ITestObject - Containes a single method that helps to identify when the problem arises. A class TestObjectContainer - A java bean that has a single property of type ITestObject A class TestObject that implements ITestOject

The testing performed is: FirstTest - A Junit test class with a single test method that simply loads a spring XML context file first-test.xml and closes the context SecondTest - A Junit test class with a single test method that

The environment is: Windows 7 Java 8 JUnit 4.12 Spring 4.1.6 Release JMockit - 1.6 or 1.16 (we had been using 1.6 and tried 1.16 when problems arose) CXF 3.0.4 Eclipse Luna

Setup A

Run SecondTest on its own - PASS - confirms the ITestObject is mocked.

Scenario A2

Run FirstTest then SecondTest as a single execution (I selected the package to run) in eclipse (It is hard to know what order eclipse/junit will run the tests) - SecondTest FAILs - it is attempting to connect with the CXF client proxy.

Setup B

As setup A, but instead of first-test.xml having a CXF client proxy, it has a POJO of type TestObject

Scenario B1

Run FirstTest then SecondTest as a single execution in eclipse -PASS

Setup C

Run SecondTest on its own - FAIL (This passes on JMockit 1.6)

Setup D

Run SecondTest on its own - PASS

So, trying to summarise the factors involved:

Apologies for the length of post and amount of detail, but hopefully will aid diagnosis.

Many thanks,

Brian.

Files

src/main/java/com/example/ITestObject.java

package com.example;
import javax.jws.WebService;

@WebService
public interface ITestObject 
{
    public boolean isMocked();
}

src/main/java/com/example/TestObject.java

package com.example;

import org.apache.log4j.Logger;

public class TestObject implements ITestObject
{
    private static final Logger log = Logger.getLogger(TestObject.class);

    public boolean isMocked()
    {
        log.info("In Real impl");
        return false;
    }
}

src/main/java/com/example/TestObjectContainer.java

package com.example;

public class TestObjectContainer 
{
    private ITestObject testObject;

    public ITestObject getTestObject()
    {
        return testObject;
    }
    public void setTestObject(ITestObject testObject)
    {
        this.testObject = testObject;
    }
}

src/test/java/com/example/FirstTest.java

package com.example;

import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class FirstTest 
{
    @Test
    public void test1()
    {
        String configFileName = "/first-test.xml";
        ClassPathXmlApplicationContext configContext = new ClassPathXmlApplicationContext(configFileName);
        configContext.close();
      }
}

src/test/java/com/example/SecondTest.java

package com.example;

import mockit.Capturing;
import mockit.NonStrictExpectations;

import org.junit.Assert;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SecondTest 
{
     @Capturing
    ITestObject mockObject;
    @Test
    public void secondTest() throws Exception
    {
        new NonStrictExpectations()
        {{
            mockObject.isMocked(); result= true;
        }};

        String configFileName = "/second-test.xml";
        ClassPathXmlApplicationContext configContext = new ClassPathXmlApplicationContext(configFileName);

        Assert.assertTrue("Direct access to mock is not working",mockObject.isMocked());

        TestObjectContainer springBeanObject = configContext.getBean("secondTestContainer",TestObjectContainer.class);
        Assert.assertTrue("Spring bean access to mock is not working",springBeanObject.getTestObject().isMocked());

        configContext.close();
    }
}

src/test/resources/first-test.xml

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jaxws="http://cxf.apache.org/jaxws"

    xsi:schemaLocation=
    "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd         
    http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd
        ">

    <bean name="firstTestContainer" class="com.example.TestObjectContainer">
        <property name="testObject" >

            <!-- Choose either the jaxws bean, or the POJO bean -->
            <jaxws:client 
                id="test-object-1"
                serviceClass="com.example.ITestObject"
                address="http://nohost1/path" />

<!--             <bean class="com.example.TestObject" /> -->

        </property>
    </bean>
</beans>

src/test/resources/second-test.xml

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jaxws="http://cxf.apache.org/jaxws"

    xsi:schemaLocation=
    "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd         
    http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd
        ">

    <bean name="secondTestContainer" class="com.example.TestObjectContainer">
        <property name="testObject" >

            <jaxws:client 
                id="test-object-2"
                serviceClass="com.example.ITestObject"
                address="http://nohost2/path" />

<!--             <bean class="com.example.TestObject" /> -->
        </property>
    </bean>
</beans>

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>repo</artifactId>
    <packaging>jar</packaging>
    <version>0.0.1-SNAPSHOT</version>
    <name>Mocking issue repo</name>
    <url />

    <properties>
        <spring.version>4.1.6.RELEASE</spring.version>
        <cxf.version>3.0.4</cxf.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>com.googlecode.jmockit</groupId>
            <artifactId>jmockit</artifactId>
            <version>1.6</version>
            <scope>test</scope>
        </dependency>

<!--         <dependency> -->
<!--             <groupId>org.jmockit</groupId> -->
<!--             <artifactId>jmockit</artifactId> -->
<!--             <version>1.16</version> -->
<!--             <scope>test</scope> -->
<!--         </dependency>       -->

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-frontend-jaxws</artifactId>
            <version>${cxf.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-transports-http</artifactId>
            <version>${cxf.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.1</version>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                    </configuration>
            </plugin>
        </plugins>
    </build>

</project>
rliesenfeld commented 9 years ago

Thanks, that's was a very good problem report!

What happens here is that dynamically generated classes are intentionally excluded from being mocked by a @Capturing mock field. And in this case, a "sun.com.proxy.$Proxy8" is generated at runtime; this is a proxy class which implements ITestObject; it has a null class loader and a null protection domain. JMockit automatically excludes all such classes from consideration, to avoid potential cases of unintended mocking. Note the generated proxy class does not extend "TestObject", which would get mocked if it was eventually loaded during the test; but it's not.

So, this is a limitation of the @Capturing feature; it only mocks "normal" classes that get loaded from a ".class" file. The limitation could be easily eliminated, but doing so could bring unforeseen consequences.

I will do some experiments to see if dropping the restriction on capturing dynamic proxy classes causes problems or not.