Injectable and Mock static methods of the same class #1

Closed
ColinHebert opened this Issue Jul 11, 2014 · 7 comments

Comments

2 participants
@ColinHebert

This test used to work with JMockit 1.8, stopped working with JMockit 1.9

public class InetAddressTest {
    @Injectable
    private InetAddress mockInetAddress;

    @Test
    public void testInetAddressMock() throws Exception{
        new NonStrictExpectations(InetAddress.class) {{
            InetAddress.getLocalHost();
            result = mockInetAddress;
        }};

        Assert.assertSame(InetAddress.getLocalHost(), mockInetAddress);
    }
}

The problem here is that JMockit complains about the class being already mocked.

Class is already mocked: class java.net.InetAddress

This is fair enough, but what is the recommended approach when I need to mock a static method from a class while having an Injectable value as well?

@rliesenfeld

This comment has been minimized.

Show comment
Hide comment
@rliesenfeld

rliesenfeld Jul 14, 2014

Member

You can write the test with @Cascading, which is simpler:

public class InetAddressTest {
@Cascading
private InetAddress mockInetAddress;

@Test
public void testInetAddressMock() throws Exception {
    Assert.assertSame(InetAddress.getLocalHost(), mockInetAddress);
}

}

That said, not mocking InetAddress would be better, if that's an option.

On Fri, Jul 11, 2014 at 8:39 PM, Colin Hebert notifications@github.com
wrote:

This test used to work with JMockit 1.8, stopped working with JMockit 1.9

public class InetAddressTest {
@Injectable
private InetAddress mockInetAddress;

@Test
public void testInetAddressMock() throws Exception{
    new NonStrictExpectations(InetAddress.class) {{
        InetAddress.getLocalHost();
        result = mockInetAddress;
    }};

    Assert.assertSame(InetAddress.getLocalHost(), mockInetAddress);
}

}

The problem here is that JMockit complains about the class being already
mocked.

Class is already mocked: class java.net.InetAddress

This is fair enough, but what is the recommended approach when I need to
mock a static method from a class while having an Injectable value as
well?


Reply to this email directly or view it on GitHub
#1.

Member

rliesenfeld commented Jul 14, 2014

You can write the test with @Cascading, which is simpler:

public class InetAddressTest {
@Cascading
private InetAddress mockInetAddress;

@Test
public void testInetAddressMock() throws Exception {
    Assert.assertSame(InetAddress.getLocalHost(), mockInetAddress);
}

}

That said, not mocking InetAddress would be better, if that's an option.

On Fri, Jul 11, 2014 at 8:39 PM, Colin Hebert notifications@github.com
wrote:

This test used to work with JMockit 1.8, stopped working with JMockit 1.9

public class InetAddressTest {
@Injectable
private InetAddress mockInetAddress;

@Test
public void testInetAddressMock() throws Exception{
    new NonStrictExpectations(InetAddress.class) {{
        InetAddress.getLocalHost();
        result = mockInetAddress;
    }};

    Assert.assertSame(InetAddress.getLocalHost(), mockInetAddress);
}

}

The problem here is that JMockit complains about the class being already
mocked.

Class is already mocked: class java.net.InetAddress

This is fair enough, but what is the recommended approach when I need to
mock a static method from a class while having an Injectable value as
well?


Reply to this email directly or view it on GitHub
#1.

@ColinHebert

This comment has been minimized.

Show comment
Hide comment
@ColinHebert

ColinHebert Jul 14, 2014

I'd love to not mock InetAddress, the problem is, InetAddress.getLocalHost().getCanonicalHostName() in Java does a lookup on the hostname which may take quite some time (or even fail) depending on some network settings. To make sure this is handled properly by the application I do not have a better solution right now.

I'd love to not mock InetAddress, the problem is, InetAddress.getLocalHost().getCanonicalHostName() in Java does a lookup on the hostname which may take quite some time (or even fail) depending on some network settings. To make sure this is handled properly by the application I do not have a better solution right now.

@ColinHebert

This comment has been minimized.

Show comment
Hide comment
@ColinHebert

ColinHebert Jul 14, 2014

Regarding the solution itself, my bad I should have given a more detailed example of what I need:

public class InetAddressTest {
    @Injectable
    private InetAddress mockInetAddressLocal;
    @Injectable
    private InetAddress mockInetAddressRemote;

    @Test
    public void testInetAddressMock() throws Exception {
        new NonStrictExpectations(InetAddress.class) {{
            InetAddress.getLocalHost();
            result = mockInetAddressLocal;
            InetAddress.getByName("bing.com");
            result = mockInetAddressRemote;
        }};

        Assert.assertSame(InetAddress.getLocalHost(), mockInetAddressLocal);
        Assert.assertSame(InetAddress.getByName("bing.com"), mockInetAddressRemote);
        Assert.assertNotSame(InetAddress.getByName("bing.com"), InetAddress.getLocalHost());
        // This is doing an actual resolution, not using any mocks.
        Assert.assertEquals(InetAddress.getByName("reddit.com").getCanonicalHostName(), "reddit.com");
    }
}

In this case, the resolution to reddit.com still works with the default behaviour (using 1.8), which is perfect as it's exactly what I need. The problem with mocking classes such as InetAddress is that some subsystems might depend on it, so the less modifications to the default behaviour, the better.

EDIT: changed google.com to reddit.com (google tends to have servers with different hostnames), that doesn't change the point though.

Regarding the solution itself, my bad I should have given a more detailed example of what I need:

public class InetAddressTest {
    @Injectable
    private InetAddress mockInetAddressLocal;
    @Injectable
    private InetAddress mockInetAddressRemote;

    @Test
    public void testInetAddressMock() throws Exception {
        new NonStrictExpectations(InetAddress.class) {{
            InetAddress.getLocalHost();
            result = mockInetAddressLocal;
            InetAddress.getByName("bing.com");
            result = mockInetAddressRemote;
        }};

        Assert.assertSame(InetAddress.getLocalHost(), mockInetAddressLocal);
        Assert.assertSame(InetAddress.getByName("bing.com"), mockInetAddressRemote);
        Assert.assertNotSame(InetAddress.getByName("bing.com"), InetAddress.getLocalHost());
        // This is doing an actual resolution, not using any mocks.
        Assert.assertEquals(InetAddress.getByName("reddit.com").getCanonicalHostName(), "reddit.com");
    }
}

In this case, the resolution to reddit.com still works with the default behaviour (using 1.8), which is perfect as it's exactly what I need. The problem with mocking classes such as InetAddress is that some subsystems might depend on it, so the less modifications to the default behaviour, the better.

EDIT: changed google.com to reddit.com (google tends to have servers with different hostnames), that doesn't change the point though.

@rliesenfeld

This comment has been minimized.

Show comment
Hide comment
@rliesenfeld

rliesenfeld Jul 14, 2014

Member

This is a tough one.

If the duplicate mocking of InetAddress is allowed, then what to make of a call to "mockInetAddressRemote.getCanonicalHostName()"? Should it be mocked according to the @Injectable mocking, or according to the partial mocking? In the first case, this method call would simply return null without doing anything; in the second, it would execute the real implementation of "getCanonicalHostName()", since no expectation was recorded for it.

Incidentally, I noticed that if that call is made using JMockit 1.8, a JVM crash occurs! I will need to investigate it later.

Member

rliesenfeld commented Jul 14, 2014

This is a tough one.

If the duplicate mocking of InetAddress is allowed, then what to make of a call to "mockInetAddressRemote.getCanonicalHostName()"? Should it be mocked according to the @Injectable mocking, or according to the partial mocking? In the first case, this method call would simply return null without doing anything; in the second, it would execute the real implementation of "getCanonicalHostName()", since no expectation was recorded for it.

Incidentally, I noticed that if that call is made using JMockit 1.8, a JVM crash occurs! I will need to investigate it later.

@ColinHebert

This comment has been minimized.

Show comment
Hide comment
@ColinHebert

ColinHebert Jul 14, 2014

I would expect the first behaviour. The @Injectable behaviour should take precedence over the partial mocking (so null as a result).
Not because it fits my use case, but because I would expect @Injectable to be considered as "more specific" (applies to one instance) whereas partial mocking (or @Mocked for that matter) is more broad (applies to the entire class).

I would expect the first behaviour. The @Injectable behaviour should take precedence over the partial mocking (so null as a result).
Not because it fits my use case, but because I would expect @Injectable to be considered as "more specific" (applies to one instance) whereas partial mocking (or @Mocked for that matter) is more broad (applies to the entire class).

@rliesenfeld

This comment has been minimized.

Show comment
Hide comment
@rliesenfeld

rliesenfeld Jul 14, 2014

Member

Yes, it makes more intuitive sense, except that partial mocking is applied later during the execution of the test, so pre-JMockit 1.9 it would always override the @Injectable mocking.

I am considering changing the semantics in cases like this, so that instance methods already mocked through an @Injectable are disregarded if the same class is partially mocked later in the test. But it needs more thinking; an idea that occurs is that the use of a "MockUp" may be a better choice here.

Member

rliesenfeld commented Jul 14, 2014

Yes, it makes more intuitive sense, except that partial mocking is applied later during the execution of the test, so pre-JMockit 1.9 it would always override the @Injectable mocking.

I am considering changing the semantics in cases like this, so that instance methods already mocked through an @Injectable are disregarded if the same class is partially mocked later in the test. But it needs more thinking; an idea that occurs is that the use of a "MockUp" may be a better choice here.

@rliesenfeld rliesenfeld self-assigned this Jul 15, 2014

@rliesenfeld

This comment has been minimized.

Show comment
Hide comment
@rliesenfeld

rliesenfeld Jul 15, 2014

Member

The current semantics will be changed so that @Injectable instances are unaffected by an eventual partial mocking of the same class.

Member

rliesenfeld commented Jul 15, 2014

The current semantics will be changed so that @Injectable instances are unaffected by an eventual partial mocking of the same class.

rliesenfeld added a commit that referenced this issue Jul 20, 2014

Enhancement: dynamic partial mocking now leaves @Injectable instances…
… unaffected, if there are any of the same class. Fixes issue #1.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment