How to return a real object from a mocked construction with Mockito

71 views Asked by At

I want to return a real object from a mocked construction using Mockito 5.9, but nothing I've tried is working.

I first tried this:

    private static class Foo {
        private final int field;

        Foo(int field) {
            this.field = field;
        }

        int getField() {
            return field;
        }
    }

    @Test
    public void test() {
        Foo spy = new Foo(5);
        try (var ignored = mockConstruction(Foo.class, withSettings()
                .spiedInstance(spy))) {
            Foo foo = new Foo(0);
            int field = foo.getField();
            Assert.assertEquals(5, field);
        }
    }

But the test fails when I run it, saying:

java.lang.AssertionError: 
Expected :5
Actual   :0

Which is the opposite of what I need.

I also tried this for the test code:

    @Test
    public void test() {
        try (var ignored = mockConstruction(Foo.class, withSettings()
                .useConstructor(5)
                .defaultAnswer(CALLS_REAL_METHODS))) {
            Foo foo = new Foo(0);
            int field = foo.getField();
            Assert.assertEquals(5, field);
        }
    }

But it fails with the same problem. I can understand why this specific option might not work, because calling the constructor might trigger the stubbed behavior, which wouldn't make sense.

A third thing I tried was this, but same problem:

    @Test
    public void test() {
        Foo spy = mock(Foo.class, withSettings()
                .useConstructor(5)
                .defaultAnswer(CALLS_REAL_METHODS));
        try (var ignored = mockConstruction(Foo.class, withSettings()
                .spiedInstance(spy))) {
            Foo foo = new Foo(0);
            int field = foo.getField();
            Assert.assertEquals(5, field);
        }
    }

It seems the mockConstruction() call is ignoring the settings I'm configuring, which seems like a bug. Any idea if this is a bug or if there's another way to do this?

There are definitely design problems with the real code I'm testing, but I'm checking to see if Mockito has a bug regardless.

1

There are 1 answers

0
gmifflen On BEST ANSWER

directly manipulating constructor behavior with mockConstruction() to return real objects with specific states isn't supported, a good approach might involve focusing on what can be controlled, how objects behave once they're constructed.

Step 1: seize the means of production (write a factory class or methpd)

    // this allows you to mock the factory instead of the constructor directly.
    public class FooFactory {
        public Foo createFoo(int field) {
            return new Foo(field);
        }
    }

Step 2: use the FooFactory

    public class Bar {
      private FooFactory fooFactory;

      // injection of the factory
      public Bar(FooFactory fooFactory) {
        this.fooFactory = fooFactory;
      }

      public void performAction() {
        Foo foo = fooFactory.createFoo(10); // make Foo with the Factory
        // do some kung Foo
      }
    }

Step 3: engage in mockery (mock the Factory in some tests)

    public class YourTestClass {
      private FooFactory fooFactory = mock(FooFactory.class);
      private YourClassUnderTest classUnderTest =
          new YourClassUnderTest(fooFactory); // dependency injection

      @Test
      public void testFooCreationWithMockedFactory() {
        Foo fooWith5 = new Foo(5);

        when(fooFactory.createFoo(anyInt()))
            .thenReturn(fooWith5); // control the Foo object

        // trigger Foo creation
        int field = classUnderTest.someMethodThatCreatesFoo();

        Assert.assertEquals(5, field); // verify
      }
    }