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

ClassCastException in paremetrized type (tried with 1.28 and 1.31) #413

Closed
karypid opened this Issue Apr 21, 2017 · 0 comments

Comments

2 participants
@karypid

karypid commented Apr 21, 2017

Hello,

I've come across what I think is an issue with generics. Below is a self-contained test that demonstrates the problem. The method test1() runs fine whereas test2() throws a ClassCastException which I think should not happen:

package playground;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import mockit.Expectations;
import mockit.Mocked;

public class PlayTest {
    static interface User { public String getUserId(); }
    static interface UserConfig { Map<String, String> getUserConfigs(); }

    static class UserRepo {
        Map<String, User> userCache;
        Map<String, UserConfig> userConfigCache;

        public User getUser(String key) {
            return userCache.get(key);
        }

        public Map<String, String> getPreferences(String key) {
            final User user = userCache.get(key);
            if (user != null) {
                final String userId = user.getUserId();
                final UserConfig userConfig = userConfigCache.get(userId);
                if (userConfig != null) {
                    return Collections.unmodifiableMap(userConfig.getUserConfigs());
                }
            }
            return Collections.emptyMap();
        }
    }

    @Mocked Map<String, User> userCache;
    @Mocked Map<String, UserConfig> userConfigCache;
    @Mocked User user;
    UserRepo userRepo;

    @Before
    public void setup() {
        userRepo = new UserRepo();
        userRepo.userCache = userCache;
        userRepo.userConfigCache = userConfigCache;
    }

    @Test
    public void test1() {
        final String userName = "fooUser";
        final String userId = "123456";
        final Map<String,String> prefs = new HashMap<>();
        prefs.put("foo", "bar");
        final UserConfig userConfig = new UserConfig() {
            @Override
            public Map<String, String> getUserConfigs() {
                return prefs;
            }
        };

        new Expectations() /* @formatter:off */ {{
            userCache.get(userName); result = user;
            user.getUserId(); result = userId;
            userConfigCache.get(userId); result = userConfig;
        }}; // @formatter:on

        final Map<String, String> preferences = userRepo.getPreferences(userName);

        Assert.assertNotNull(preferences);
        Assert.assertFalse(preferences.isEmpty());
    }

    @Test
    public void test2() {
        final String userName = "fooUser";
        final String userId = "123456";

        new Expectations() /* @formatter:off */ {{
            userCache.get(userName); result = user;
            user.getUserId(); result = userId;
            // UNCOMMENT TO FIX: userConfigCache.get(userId); result = null;
        }}; // @formatter:on

        final Map<String, String> preferences = userRepo.getPreferences(userName);

        Assert.assertNotNull(preferences);
        Assert.assertTrue(preferences.isEmpty());
    }

    static interface CustomMap<K, V> {
        V get(K userId);
    }

}

Basically what is happening is this: I am mocking the type Map for these two variables:

    @Mocked Map<String, User> userCache;
    @Mocked Map<String, UserConfig> userConfigCache;

It appears to me that when JMockIt initializes these two mocks, it erroneously picks up the type parameters of the first declaration and associates them with both mock instances. It remains in that incorrect state until you configure explicit expectations for the mocks.

If you happen to write a test that doesn't explicitly set a return value (i.e. rely on the default behavior of returning null) you get a class cast exception.

As you can see test2() can be fixed by simply removing the commented out line:

            // UNCOMMENT TO FIX: userConfigCache.get(userId); result = null;

Without this line you get:

java.lang.ClassCastException: playground.$Proxy6 cannot be cast to playground.PlayTest$UserConfig
	at playground.PlayTest$UserRepo.getPreferences(PlayTest.java:30)
	at playground.PlayTest.test2(PlayTest.java:87)
...

@karypid karypid changed the title from ClassCastException in paremetrized type to ClassCastException in paremetrized type (tried with 1.28 and 1.31) Apr 21, 2017

@rliesenfeld rliesenfeld self-assigned this Apr 21, 2017

@rliesenfeld rliesenfeld added the bug label Apr 21, 2017

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