简体   繁体   中英

Static factory methods and mocking

How do you reconcile using static factory methods and mocking?

Many people would just say: Don't use static factory methods, use DI instead.

Well, sometimes you cannot avoid static factory methods. Consider the following use cases, which should be familiar:

Imagine you have a class called Option , like in scala. You can't avoid using a static factory method if you want to reuse same instance for all absent values.

As soon as you go new Option(null) you create a new option object, you cannot return the same object over and over again.

Similar use case is the Integer.valueOf() which will reuse integer objects for values below 128. Impossible to do without using a static factory method.

Another advantage is that factory methods are more descriptive than new keyword.

So how do you guys deal with having to use static factory methods and at the same time wanting to use inheritance and mocks?

Thank you.

Mocking out static methods is possible using PowerMock . Consider the following example from their Wiki page :

@Test
public void testRegisterService() throws Exception {
    long expectedId = 42;

    // We create a new instance of test class under test as usually.
    ServiceRegistartor tested = new ServiceRegistartor();

    // This is the way to tell PowerMock to mock all static methods of a
    // given class
    mockStatic(IdGenerator.class);

    /*
     * The static method call to IdGenerator.generateNewId() expectation.
     * This is why we need PowerMock.
     */
    expect(IdGenerator.generateNewId()).andReturn(expectedId);

    // Note how we replay the class, not the instance!
    replay(IdGenerator.class);

    long actualId = tested.registerService(new Object());

    // Note how we verify the class, not the instance!
    verify(IdGenerator.class);

    // Assert that the ID is correct
    assertEquals(expectedId, actualId);
}

It's even possible to mock out only one particular method and leave the rest as is, using partial mocking .

Since it's a theorical question, I will make a theorical answer. The factory paradigm is the building point for another theory: the Injection. If your created objects are injected when needed, then you only have to inject your mocked objects to do all your tests. There alot of good books / web pages that can help you to get started on that.

My first option is to avoid the need to mock anything, so having static factory methods or not makes no difference.

That said, if I do want or need to mock them, then I just do it. For example, consider you are testing a JSF-based web application, and you want to mock the javax.faces.context.FacesContext object. I would write the following in a test, using the JMockit library (which I happen to develop):

@Test
public void exampleTest(@Mocked final FacesContext ctx) {
    // Call the code under test, which will at some point
    // call FacesContext.getCurrentInstance(), then add an
    // error message for display in the web page.

    new Verifications() {{
        FacesMessage msg;
        ctx.addMessage(null, msg = withCapture());

        assertEquals("The expected error message.", msg.getSummary());
        assertNotNull(msg.getDetail());
    }};
}

In this example, Faces.getCurrentInstance() is the static factory method, which will automatically return a mock FacesContext instance once the class is mocked.

We simply avoid using static factory methods and we use dependency injection instead.

If java had been designed with DI in mind from the start, then Integer.valueOf() would have been:

  • integerType.valueOf() where integerType would be an injected dependency, or

  • typeSystem.getInteger().valueOf() where typeSystem would be an injected dependency, or

  • environment.getTypeSystem().getInteger().getFactory() where environment would be an injected dependency.

There is nothing that you can do with static factories that you cannot do with diligent use of dependency injection.

Now, once someone makes something available only via a static factory method, they are essentially coercing you to take the static road. This is unfortunate. But you can still wrap the static stuff in instances of your own device, and then inject those instances as dependencies into your production code, and then have your unit tests exercise those instances, avoiding the need to do such ungodly hacks as mocking static methods.

For example, you can wrap System.out in some StandardConsole object implementing some Console interface, and inject that Console interface as a dependency into your application.

(And if you do that, I would even add that you may proceed and configure your version control system to reject any attempts to commit code containing the string "System.out". [evil grin])

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM