jmock-developers / jmock-library

An expressive Mock Object library for Test Driven Development
http://www.jmock.org
BSD 3-Clause "New" or "Revised" License
133 stars 70 forks source link

JUnit 5 and Nested classes #189

Open acarpe opened 4 years ago

acarpe commented 4 years ago

Suppose to have this code: a context declared on the enclosing class and one or more nested classes. It now fails with a no Mockery found in test class

public class NestedJUnit5TestThatDoesNotSatisfyExpectations
{
  @RegisterExtension
  JUnit5Mockery context = new JUnit5Mockery();

  Runnable runnable = context.mock(Runnable.class);

  @Nested
  class NestedClass {
    @Test
    public void doesNotSatisfyExpectations() {
      context.checking(new Expectations() {{
        oneOf (runnable).run();
      }});

      // Return without satisfying the expectation for runnable.run()
    }
  }
}

I tried to make pass this new test

    @Test
    public void testTheJUnit5TestRunnerLooksForTheMockeryInUpperClassOfInnerClasses() {
        listener.runTestIn(NestedJUnit5TestThatDoesNotSatisfyExpectations.class);
        listener.assertTestFailedWith(AssertionError.class);
    }

And as first step I modified the AllDeclaredFields to look in the enclosing classes

public class AllDeclaredFields {
    public static List<Field> in(Class<?> clazz) {
        final ArrayList<Field> fields = new ArrayList<Field>();
        for (Class<?> c = clazz; c != Object.class; c = c.getSuperclass()) {
            fields.addAll(asList(c.getDeclaredFields()));
        }
        for (Class<?> c = clazz.getEnclosingClass(); c != null; c = c.getEnclosingClass()) {
            fields.addAll(asList(c.getDeclaredFields()));
        }
        return fields;
    }
}

but then inside the checkMockery, I don't know how to get the instance of the enclosing types (it has the this$0 field but it's not accessible) to check that the field is not null

if(mockeryField.get(context.getRequiredTestInstance()) == null) {
    throw new IllegalStateException("JUnit5Mockery field should not be null");
}

And I was stuck there. The only workaround I found was to declare a field inside the nested class and initialize it with the outer field, like this:

package org.jmock.junit5.testdata.jmock.acceptance;

import org.jmock.Expectations;
import org.jmock.auto.Mock;
import org.jmock.junit5.JUnit5Mockery;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

public class NestedJUnit5TestThatDoesNotSatisfyExpectations
{
  @RegisterExtension
  JUnit5Mockery outerContext = new JUnit5Mockery();

  Runnable runnable = context.mock(Runnable.class);

  @Nested
  class NestedClass {
    JUnit5Mockery context = outerContext;

    @Test
    public void doesNotSatisfyExpectations() {
      context.checking(new Expectations() {{
        oneOf (runnable).run();
      }});

      // Return without satisfying the expectation for runnable.run()
    }
  }
}
olibye commented 4 years ago

I'm happy with your solution to the problem. I feel that a test case with additional as test cases inner classes feels wrong. I'd use TestSuites or annotations to group TestCases.

Oli

On Thu, 30 Apr 2020 at 19:27, Antonio Carpentieri notifications@github.com wrote:

Suppose to have this code: a context declared on the enclosing class and one or more nested classes. It now fails with a no Mockery found in test class

public class NestedJUnit5TestThatDoesNotSatisfyExpectations { @RegisterExtension JUnit5Mockery context = new JUnit5Mockery();

Runnable runnable = context.mock(Runnable.class);

@Nested class NestedClass { @Test public void doesNotSatisfyExpectations() { context.checking(new Expectations() {{ oneOf (runnable).run(); }});

  // Return without satisfying the expectation for runnable.run()
}

} }

I tried to make pass this new test

@Test
public void testTheJUnit5TestRunnerLooksForTheMockeryInUpperClassOfInnerClasses() {
    listener.runTestIn(NestedJUnit5TestThatDoesNotSatisfyExpectations.class);
    listener.assertTestFailedWith(AssertionError.class);
}

And as first step I modified the AllDeclaredFields to look in the enclosing classes

public class AllDeclaredFields { public static List in(Class<?> clazz) { final ArrayList fields = new ArrayList(); for (Class<?> c = clazz; c != Object.class; c = c.getSuperclass()) { fields.addAll(asList(c.getDeclaredFields())); } for (Class<?> c = clazz.getEnclosingClass(); c != null; c = c.getEnclosingClass()) { fields.addAll(asList(c.getDeclaredFields())); } return fields; } }

but then inside the checkMockery, I don't know how to get the instance of the enclosing types (it has the this$0 field but it's not accessible) to check that the field is not null

if(mockeryField.get(context.getRequiredTestInstance()) == null) { throw new IllegalStateException("JUnit5Mockery field should not be null"); }

And I was stuck there. The only workaround I found was to declare a field inside the nested class and initialize it with the outer field, like this:

package org.jmock.junit5.testdata.jmock.acceptance;

import org.jmock.Expectations; import org.jmock.auto.Mock; import org.jmock.junit5.JUnit5Mockery; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension;

public class NestedJUnit5TestThatDoesNotSatisfyExpectations { @RegisterExtension JUnit5Mockery outerContext = new JUnit5Mockery();

Runnable runnable = context.mock(Runnable.class);

@Nested class NestedClass { JUnit5Mockery context = outerContext;

@Test
public void doesNotSatisfyExpectations() {
  context.checking(new Expectations() {{
    oneOf (runnable).run();
  }});

  // Return without satisfying the expectation for runnable.run()
}

} }

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/jmock-developers/jmock-library/issues/189, or unsubscribe https://github.com/notifications/unsubscribe-auth/AACAVUE5357MORJ3ZXNSUZDRPHGJJANCNFSM4MWOOCQQ .