New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@Capturing not always able to mock objects. #161

Closed
brianruss opened this Issue Apr 10, 2015 · 1 comment

Comments

2 participants
@brianruss

brianruss commented Apr 10, 2015

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

  • Has @Capturing on ITestObject types
  • loads spring XML context file second-test.xml
  • Gets a named bean from the context of type TestObjectContainer
  • Checks that the contained ITestObject bean is mocked.

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

  • JMockit 1.6 (This was the version we were using when we first encountered the problem.
  • The first-test.xml configuration has a TestObjectContainer bean containing an ITestObject that is actually a CXF proxy client for a web service.
  • The second-test.xml configuration also has a TestObjectContainer bean containing an ITestObject that is actually a CXF proxy client for a web service.

Scenario A1

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

  • JMockit 1.16
  • The second-test.xml configuration has a TestObjectContainer bean containing an ITestObject that is actually a CXF proxy client for a web service.

Scenario C1

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

Setup D

  • JMockit 1.16
  • The second-test.xml configuration has a TestObjectContainer bean containing an ITestObject that has a POJO of type TestObject.

Scenario D1

Run SecondTest on its own - PASS

So, trying to summarise the factors involved:

  • Using a CXF bean rather than a POJO causes failure (Scenarios A2 -> B1 and C1 -> D1)
  • Running tests in isolation or as part of a set of tests causes failure (A1 -> A2)
  • Using the latest JMockit can cause a failure (A1 -> D1)

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

This comment has been minimized.

Show comment
Hide comment
@rliesenfeld

rliesenfeld Apr 10, 2015

Member

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.

Member

rliesenfeld commented Apr 10, 2015

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment