Skip to content
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

Generics type inference minor issue #239

Open
josephlbarnett opened this issue Jan 28, 2019 · 8 comments
Open

Generics type inference minor issue #239

josephlbarnett opened this issue Jan 28, 2019 · 8 comments
Assignees

Comments

@josephlbarnett
Copy link

I've been using easymock 3.4 from Kotlin tests, where the mock can be declared like:

val mockEnv = EasyMock.mock(Environment::class.java)

After upgrading to easymock 4.0.2 (Likely related to #229), that fails to compile due to type inference problems, and must be replaced with either:

val mockEnv = EasyMock.mock<Environment>(Environment::class.java)

or:

val mockEnv: Environment = EasyMock.mock(Environment::class.java)

Overall a pretty minor thing, but not having to repeat the for non generics was nice.

@henri-tremblay henri-tremblay self-assigned this Jan 29, 2019
@henri-tremblay
Copy link
Contributor

Interesting. It makes sense since in Java the left-hand side it always typed. The same problem will occur in Java 10 with var I guess.

Hum... I don't really have a solution for that. Maybe I was wrong to change the typing and I should add a mockGeneric method instead. Thoughts?

@josephlbarnett
Copy link
Author

I think because of type erasure of java the only way to do this properly is with concrete TypeRef anonymous class instances as per solution 1 in the linked issue. I'm not a generics expert, but that's how I've always done, for example, binding a generic in a guice module (using their TypeLiteral class).

@sratz
Copy link

sratz commented Sep 12, 2019

#229 also made the situation much worse for IDE tooling:

Consider:

void test() {
  createMock(Something.class);
}

Then do, e.g in Eclipse, QuickFix -> Assign statement to new local variable.
You end up with

void test() {
  Object mock = createMock(Something.class);
}

instead of

void test() {
  Something mock = createMock(Something.class);
}

This applies to other tooling support such as Create method, Extract method, etc. It's very easy to create methods / variables with wrong types now.

I think relaxing the typing with #229 has much more drawbacks than benefits. In the situation with
List<String> list = mock(List.class), simply doing

@SuppressWarnings("unchecked")
List<String> list = mock(List.class)

is not that big of a deal, IMHO.

@josephlbarnett
Copy link
Author

fwiw, we've got a class that provides some inline functions to make some of this nicer: https://github.com/trib3/leakycauldron/blob/master/testing/src/main/kotlin/com/trib3/testing/LeakyMock.kt

the balance between using that class and using EasyMock directly isn't the cleanest thing in the world, but if it's helpful to anyone, it's available.

@henri-tremblay
Copy link
Contributor

@josephlbarnett How do it works? The reify version can receive List<String>.class?

@josephlbarnett
Copy link
Author

with the reified version, you can do val mockedList = LeakyMock.mock<List<String>>() to get a mocked List<String>

@croesch
Copy link

croesch commented Nov 5, 2019

I think I face the same issue that causes a java.lang.ClassCastException: com.sun.proxy.$Proxy4 cannot be cast to java.base/[Ljava.lang.Object; at runtime after upgrading to 4.0.2.

While I still don't understand the different behavior of compiler and runtime here, the following works in 3.4 and fails in 4.0.2.

  1. $ mvn archetype:generate -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4
  2. Add easymock in pom.xml:
<?xml version="1.0" encoding="UTF-8"?>

<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/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.example</groupId>
  <artifactId>example.app</artifactId>
  <version>1.0-SNAPSHOT</version>

  <name>example.app</name>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.easymock</groupId>
      <artifactId>easymock</artifactId>
      <!-- code works -->
      <!-- <version>3.4</version> -->
      <!-- code fails -->
      <version>4.0.2</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>
  1. The problematic test code calls a constructor that has both C(Object) and C(Object[]) available.
package com.example;

import static org.junit.Assert.assertTrue;

import javax.swing.tree.TreePath;
  
import org.junit.Test;
import org.easymock.EasyMock;

public class AppTest 
{
    @Test
    public void shouldCompileAndRun()
    {
      TreePath path = new TreePath( EasyMock.createNiceMock( Runnable.class ));
    }
}

I have a similar (but written in Java) approach like @josephibarnett. This works for me, but what's your suggestion, to change the test code or to improve the generic code in EasyMock?

@henri-tremblay
Copy link
Contributor

I'm biaised. I like very much the relaxed typing but I do agree it causes problems.

I'm in front of

  public static <T> T relaxedMock(Class<?> toMock) {
    return null;
  }
  
  public static <T> T strongMock(Class<T> toMock) {
    return null;
  }
  • var s = strongMock(String.class); works

  • var s = relaxedMock(String.class); doesn't, the type can't be inferred

  • var s = EasyMock.<String>relaxedMock(String.class); works

  • String s = strongMock(String.class); works

  • String s = relaxedMock(String.class); works

  • var list = strongMock(List.class); doesn't, list is now a raw type

  • var list = relaxedMock(List.class); doesn't, the type can't be inferred

  • var list = EasyMock.<List<String>>relaxedMock(List.class); works!

  • List<String> list3 = strongMock(List.class); doesn't, can't compile

  • List<String> list4 = relaxedMock(List.class); works!

So as you can see, with the relaxed way, everything can work without warning. It just requires a type witness. But I agree that for cases where the inference used to work, it's annoying.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants