2

After adding powermock (1.5.6 in combination with Easymock 3.2) to my current project (jdk 1.6.0) I get some test failures in test methods which worked perfectly fine before:

java.lang.UnsatisfiedLinkError: com.sun.imageio.plugins.jpeg.JPEGImageReader.initReaderIDs(Ljava/lang/Class;Ljava/lang/Class;Ljava/lang/Class;)V

The following code fails:

BufferedImage img = null;
try {
    img = ImageIO.read(this.getClass().getResourceAsStream("/example.jpg"));
}
catch (IOException e) {
    fail(e.getMessage());
}

The powermock page already has a bug from 2009 but no fix and no workaround. (Going back to 32Bit is nonsense since those methods work without powermock) So does anybody know how to fix this?

Update I: A switch to 32 Bit is no option and besides that this is not the problem. If I don't use PowerMock every test is working perfectly in my 64Bit JVM...

Update II: Ok here are the requested infos

Update III: Extended the class

  1. Class to be tested

    import java.awt.image.BufferedImage;
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.security.GeneralSecurityException;
    import java.security.cert.X509Certificate;
    import javax.imageio.ImageIO;
    import sun.security.x509.CertificateIssuerName;
    import sun.security.x509.CertificateSubjectName;
    import sun.security.x509.X500Name;
    import sun.security.x509.X509CertImpl;
    import sun.security.x509.X509CertInfo;
    
    public class App {
        private X509Certificate certificate = null;
    
        public ByteArrayOutputStream readImage() {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        BufferedImage img = null;
    try {
        img = ImageIO.read(this.getClass().getResourceAsStream("/example.jpg"));
        ImageIO.write(img, "png", baos);
    }
    catch (IOException e) {
        e.printStackTrace();
    }
    
    return baos;
    }
    
    public String readCertificate() throws Exception{
     this.certificate = generateCertificate();
     return this.certificate.getIssuerX500Principal().getName();
    }
    
    private static X509Certificate generateCertificate() throws   GeneralSecurityException, IOException{
          X509CertInfo info = new X509CertInfo();
          X500Name owner = new X500Name("CN=example.net");
          info.set(X509CertInfo.SUBJECT, new CertificateSubjectName(owner));
          info.set(X509CertInfo.ISSUER, new CertificateIssuerName(owner));
          return new X509CertImpl(info);
     }   
    }
    
  2. Test case:

    import org.junit.Assert;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.powermock.core.classloader.annotations.PowerMockIgnore;
    import org.powermock.core.classloader.annotations.PrepareForTest;
    import org.powermock.modules.junit4.PowerMockRunner;
    @RunWith(PowerMockRunner.class)
    @PrepareForTest(App.class)
    @PowerMockIgnore("javax.imageio.*, javax.security.*") 
    public class AppTest {
    
     @Test
     public void testApp(){
         App test = new App();
         Assert.assertNotNull(test.readImage());
         Assert.assertEquals(284506, test.readImage().size());
     }
     @Test
     public void testCertificate() throws Exception{
       App test = new App();
       test.readCertificate();
     }
    }
    
  3. Maven dependencies:

    <dependencies>
        <!-- TEST -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.easymock</groupId>
            <artifactId>easymock</artifactId>
            <version>3.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-module-junit4</artifactId>
            <version>1.5.6</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-api-easymock</artifactId>
            <version>1.5.6</version>
            <scope>test</scope>
        </dependency>
    

So if you comment the line: //@RunWith(PowerMockRunner.class) it is working. If uncommented it the above error is thrown (again!)

Lonzak
  • 9,334
  • 5
  • 57
  • 88
  • Part of the problem seems to be the different class loader of powermock. If I add @PowerMockIgnore({"com.sun.imageio.*"}) it seems to be able to get one step further but it still doesn't work... – Lonzak Feb 24 '15 at 14:54
  • I believe the method in the Error is a native method. What are you trying to achieve here? To mock ImageIO? – Harald K Feb 24 '15 at 19:30
  • I wanted to mock a private method - so I added powermock for that. After that other existing tests which suceed without powermock should work as before - that is what I am trying to achieve... – Lonzak Feb 24 '15 at 23:40
  • You might take a look into using JMockit instead. It's just one library, very simple and consistent to use whether working with interfaces or classes and I now prefer it to my previous favorite, Mockito. JMockit uses Java6's byte code instrumentation rather than java.lang.reflect.Proxy and CGLIB. See http://jmockit.org/ – unigeek Mar 02 '15 at 16:09
  • Can you give more information (dependency-tree, complete test class). I created a test (PowerMockRunner) with easymock 3.2, powermock 1.5.6, junit 4.4, based on the code you posted and it works. – René Link Mar 04 '15 at 08:21

1 Answers1

10

The solution is to tell PowerMock to ignore all JRE classes which conflict with its custom class loader. That is, add the following annotation to the test class:

@PowerMockIgnore({"javax.imageio.*", "javax.security.*"})

(Note the value attribute of the annotation takes an array of regular expressions; it does not support multiple comma-separated expressions in a single string.)

The explanation for why this is needed is that

  1. PowerMock operates by re-loading the prepared class (and also the test class) in its own custom classloader;
  2. when App calls into javax.imageio.ImageIO, it ends up trying to load & initialize the internal class com.sun.imageio.plugins.jpeg.JPEGImageReader, which then attempts to load some other com.sun.imageio classes from the caller class loader; and
  3. the class load fails because PowerMock's custom class loader apparently can't find those JRE classes (it's hard to tell exactly what happens at this point, because the loading is done by a native method in the JPEGImageReader class - maybe it attempts to load some native library as well).
Rogério
  • 16,171
  • 2
  • 50
  • 63
  • I did already try that before posting here - but unfortunatly it doesn't work either. I did update the code above (cp. Update III) to reflect the situation ... – Lonzak Mar 05 '15 at 11:22
  • 1
    It should work now, with the addition of "javax.security". Note the expression `"javax.imageio.*, javax.security.*"` is not valid. – Rogério Mar 05 '15 at 17:58
  • Ah, thanks for spotting that. I came one step further but now some other thing is not working...have to figure that one out.... – Lonzak Mar 09 '15 at 11:52