jmockit / jmockit1

Advanced Java library for integration testing, mocking, faking, and code coverage
Other
465 stars 240 forks source link

Data provider mismatch with @Mocked annotation #337

Closed AlexStasko closed 7 years ago

AlexStasko commented 8 years ago

I use JMockit 1.27 Java 1.8.92 And Windows OS

public class DummyServiceTest {

     private DummyService dummyService = new DummyService();

     @Test(expectedExceptions = {RuntimeException.class})
     public void testStore(@Mocked DummyDomain unused)  {
         new StrictExpectations() {
             {
                 new DummyDomain("TEST1");

                 try {
                     unused.exec();
                 } catch (IOException e) {
                     // never happen
                }
                result = new IOException();
             }
         };

         dummyService.store("test");
     }
}

Report prints following error:

org.testng.internal.reflect.MethodMatcherException: 
Data provider mismatch
Method: testStore([Parameter{index=0, type=by.common.model.DummyDomain, declaredAnnotations=[@mockit.Mocked(stubOutClassInitialization=false)]}])
Arguments: [(java.lang.String)]
at org.testng.internal.reflect.DataProviderMethodMatcher.getConformingArguments(DataProviderMethodMatcher.java:52)
at org.testng.internal.Invoker.injectParameters(Invoker.java:1238)
at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1131)
at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:129)
at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:112)
at org.testng.TestRunner.privateRun(TestRunner.java:749)
at org.testng.TestRunner.run(TestRunner.java:603)
at org.testng.SuiteRunner.runTest(SuiteRunner.java:368)
at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:363)
at org.testng.SuiteRunner.privateRun(SuiteRunner.java:321)
at org.testng.SuiteRunner.run(SuiteRunner.java:270)
at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:86)
at org.testng.TestNG.runSuitesSequentially(TestNG.java:1284)
at org.testng.TestNG.runSuitesLocally(TestNG.java:1209)
at org.testng.TestNG.runSuites(TestNG.java:1124)
at org.testng.TestNG.run(TestNG.java:1096)
at org.gradle.api.internal.tasks.testing.testng.TestNGTestClassProcessor.runTests(TestNGTestClassProcessor.java:130)
at org.gradle.api.internal.tasks.testing.testng.TestNGTestClassProcessor.stop(TestNGTestClassProcessor.java:80)
at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:58)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
at org.gradle.messaging.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:32)
at org.gradle.messaging.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93)
at com.sun.proxy.$Proxy2.stop(Unknown Source)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.stop(TestWorker.java:116)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
at org.gradle.messaging.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:360)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:54)
at org.gradle.internal.concurrent.StoppableExecutorImpl$1.run(StoppableExecutorImpl.java:40)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)

That problem happens with testng 6.9.11 and higher. With testng 6.9.10 this test runs successfully. I am not sure the problem is in jmockit, but might be this is an issue somewhere in proxy that jmockit creates for method parameter.

Test project is here jmockit-check.zip

Thank you, Alex

juherr commented 8 years ago

The problem is due to the new way to manage data provider parameters: https://github.com/cbeust/testng/pull/923

Maybe JMockit should upgrade its TestNG support. But let me know if a specific spi is needed to help with the integration.

Ping @nitinverma who made the 6.9.11 modification, maybe he'll have a good idea for a nice integration.

For reference, we have https://github.com/cbeust/testng/issues/600 in the backlog too which points some integration problems on dataprovider.

rliesenfeld commented 8 years ago

TestNG should provide an extensible mechanism for third-party libraries to provide their own custom arguments to test methods, without them having to resort to hacks as I had to do in JMockit. JUnit 5 (not JUnit 4) does provide such a mechanism (the ParameterResolver extension interface), and it is used in JMockit.

I will see if the new parameter resolution in TestNG 6.9.11+can be supported. From the first look, it may be difficult. I don't exclude the possibility that mock parameters in test methods won't be usable anymore with these newer versions of TestNG; in that case, JMockit users would have to limit themselves to mock fields.

nitinverma commented 8 years ago

Hi @AlexStasko, @rliesenfeld

testng is expecting an object that is an instance of 'DummyDomain' or null, as both are valid inputs to invoke this test method.

Following snippet would satisfy testng-6.9.11 for now.

   @DataProvider(name = "data")
    public Object[][] data() {
        return new Object[][]{
                new Object[]{null},
                new Object[]{new DummyDomain("from-data-provider")},
        };
    }

    @Test(dataProvider = "data", expectedExceptions = {RuntimeException.class})
    public void testStore(@Mocked DummyDomain unused) {
        System.out.println("unused:" + unused);
        new StrictExpectations() {
            {
                new DummyDomain("TEST1");

                try {
                    unused.exec();
                } catch (IOException e) {
                    // never happen
                }
                result = new IOException();
            }
        };

        dummyService.store("test");
    }

Happy to help n collaborate on this integration.

Regards,

Nitin Verma

nitinverma commented 8 years ago

Refer https://github.com/jmockit/jmockit1 if getInjectedParameter(...) returns corresponding mock object in place of the empty string. Above test should start working. What additional context would you require to build a mock?

TestNGRunnerDecorator.java

   public static final class MockParameters extends MockUp<Parameters>
   {
      @Mock
      public static void checkParameterTypes(
         String methodName, Class<?>[] parameterTypes, String methodAnnotation, String[] parameterNames) {}

      @Mock
      @Nullable
      public static Object getInjectedParameter(
         @Nonnull Invocation invocation, Class<?> c, @Nullable Method method,
         ITestContext context, ITestResult testResult)
      {
         ((MockInvocation) invocation).prepareToProceedFromNonRecursiveMock();
         Object value = Parameters.getInjectedParameter(c, method, context, testResult);

         if (value != null) {
            return value;
         }

         if (method == null) {
            // Test execution didn't reach a test method yet.
            return null;
         }

         if (method.getParameterTypes().length == 0) {
            // A test method was reached, but it has no parameters.
            return null;
         }

         if (isMethodWithParametersProvidedByTestNG(method)) {
            // The test method has parameters, but they are to be provided by TestNG, not JMockit.
            return null;
         }

         // It's a mock parameter in a test method, to be provided by JMockit.
         return "";
      }
   }
Milbor-zz commented 7 years ago

It stopped working for testng 6.12. Reopen?

    @Test
    public void test(@Injectable Runnable runnable) {
    }

org.testng.internal.reflect.MethodMatcherException: Data provider mismatch Method: test([Parameter{index=0, type=java.lang.Runnable, declaredAnnotations=[@mockit.Injectable(value=)]}]) Arguments: []

at org.testng.internal.reflect.DataProviderMethodMatcher.getConformingArguments(DataProviderMethodMatcher.java:45)
at org.testng.internal.Invoker.injectParameters(Invoker.java:1301)