When to use static methods mock?

Asked

Viewed 2,071 times

9

@Fernandoschelb recently asked the question about how to mock static methods. It raised my doubts:

  • If we can do it, when we should do it?
  • What are the cases where static method mock is beneficial?
  • When I can detect that the use of this mock is being abusive?
  • 1

    Good question... I have questions tmb... Pq don’t usually test static methods? Pq will be that the mockite does not give support, having to resort to other frameworks.... :-/

  • 1

    @Fernandoschelb, if he has someone who did it, I believe he went to meet some need. I in my limited view do not see why beyond a wrong modeling, but I believe there are situations where it is extremely positive to do this. But also that in many cases it is just abuse of the structure

  • May I have the honor to know why the negative? I wish I could improve

  • What do you mean, "detect abuse"? Despite all the discussion about static methods, I have not yet found a Pattern/rule of when or not to mock a static method. Some CI/CD processes require 80%+ Coverage code, which will probably force you to test static methods (with or without mocks). Generally mock is beneficial when the static method is stateless or does not cause "side effects". In cases where the static method is stateful, has dependencies or cause "side effect", its mock may be a shot in the foot and not represent actual behavior.

  • @Fernandoschelb there is a discussion in the mockito repository about it. Apparently they don’t want to encourage the abuse of static methods. But I believe that if the static method exists, it must be tested yes.

  • 1

    "Abuse" I wanted to use in the same sense "abuse of the use of OO" or "abuse the goto or "abuse of the non abuse of goto"; indiscriminate use/not recommended/harmful to the project. " Detecting abusive use" would then be a kind of procedure to recognize these uses by reading the code/test/satellite and related information

  • 1

    @Jeffersonquesado ah, yes! I think this will depend a little on the tools you use. I’m not sure, but in Sonar for example, maybe you can configure metrics/code Smell to detect mock abuse.

Show 2 more comments

2 answers

5


Static methods are as flexible as granite. If you need to mock a static method, it is a sign that there is something wrong with the design.

A static method ideally offers a definite and unchanging functionality. If you need to mock it to isolate your test from its dependencies, then it is either not definitive and not immutable, and therefore it should not be static, or it was not something for you to mock.

The idea of using mock is to isolate code under test from its dependencies. However, it is important to note that there are different types of dependencies and not all are worth mocking.

For example, let’s imagine this code:

package com.example.meucodigo;

public class MinhaClasse {
    public static double sincos(int a) {
        double r = Math.sin(a);
        double s = Math.cos(a);
        return r + s;
    }
}

Naturally, as you can see, it returns the sum of the sine and the cosine of a number. The proper way to test it (with Junit 5) would be like this:

package com.example.teste;

import com.example.meucodigo.MinhaClasse;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

public class TesteSinCos {
    @Test
    public void teste0() {
        Assertions.assertEquals(1.0, MinhaClasse.sincos(0));
    }

    @Test
    public void teste30() {
        Assertions.assertEquals(Math.srqt(3) / 2 + 0.5, MinhaClasse.sincos(Math.PI / 6));
    }

    @Test
    public void teste45() {
        Assertions.assertEquals(Math.sqrt(2), MinhaClasse.sincos(Math.PI / 4));
    }

    @Test
    public void teste90() {
        Assertions.assertEquals(1.0, MinhaClasse.sincos(Math.PI / 2));
    }
}

Note that in the above test no mock was used and he does not even care to know that the implementation uses Math.sin and Math.cos. Using the mock in this case would be an abuse as the use of Math.sin and Math.cos is an immutable and internal part of the method exercised. If we mock these functions, we would basically be testing a simple addition method, and not testing the functionality actually offered by the method. Moreover, under the point of who uses the method, external to it, the dependency of a given static method is an encapsulated detail of the implementation. Design the API so that it can be mocked (i.e., by injecting the sine and co-sine methods into the class MinhaClasse) is bad and results in increased complexity without real benefit, making the class in question more difficult to use, more difficult to be instantiated and more difficult to test. You should design classes and methods that can be tested without violating their natural encapsulation.

However, obviously, there are several cases where the use of mocks is justified, especially when the dependency is somewhat polymorphic, excessively complex, which depends on external state and/or which have methods that when used, produce side effects on objects other than their parameters. For example:

import java.sql.Connection;

public class Conexao {
    public static Connection conectar() {
        // ...
    }
}
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

public class ClienteDAO {

    private static final String INADIMPLENTES_SQL = "SELECT * FROM BLABLA";

    public List<Cliente> listarInadimplentes() {
        List<Cliente> inadimplentes = new ArrayList<>();
        try (
            Connection c = Conexao.conectar();
            PreparedStatement ps = c.prepareStatement(INADIMPLENTES_SQL);
            ResultSet rs = ps.executeQuery();
        )
        {
            // ...
        }
        return inadimplentes;
    }
}

In that case, the ClienteDAO depends on the static method Conexao.conectar(). It also depends on a String that is plated in your code.

This is a typical case where the use of the mock is desirable and the use of the static method causes problems. The best solution would be to eliminate the use of static method and use dependency injection instead:

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.function.Supplier;

public class ClienteDAO {

    private static final String INADIMPLENTES_SQL = "SELECT * FROM BLABLA";

    private final Supplier<Connection> conector;

    public ClienteDao(Supplier<Connection> conector) {
        this.conector = conector;
    }

    public List<Cliente> listarInadimplentes() {
        List<Cliente> inadimplentes = new ArrayList<>();
        try (
            Connection c = conector.get();
            PreparedStatement ps = c.prepareStatement(INADIMPLENTES_SQL);
            ResultSet rs = ps.executeQuery();
        )
        {
            while (rs.next()) {
                // ...
            }
        }
        return inadimplentes;
    }
}

And then, you can mock the Supplier<Connection> with an implementation that provides a Connection mock that produces PreparedStatements mocked which produce ResultSets mocked. This is still a lot of work, but it shouldn’t be too hard to do with the mockite and free you from the need to have a real database and gives you control over the dependencies.

In general, the approach to be used with methods that manufacture polymorphic objects is this. Instead of relying on a static factory method or a Singleton, you exchange it for dependency injection. That is, the solution is not to mock the static method or Singleton, but to eliminate its use!

There is still static SQL in the above code. Strictly speaking this SQL should be a detail of the encapsulated implementation in ClienteDAO. The problem is that it "leaks" and becomes visible to its dependencies. The solution would be to put it in a separate class and inject it (it has the advantage of making Sqls more flexible, but it increases complexity) or to put the modifier public in it (simpler but less flexible and can harm the encapsulation).

It’s true that there are many things a class can do that depend on static methods, builders and singletons that if they were all refactored to be injected as dependencies, would end up producing something with so many dependency injection points and so much over-polymorphism that it would virtually no longer be usable. Part of the solution for this is:

  • Preferring to use immutable data classes, something that greatly simplifies testing, greatly eliminates the need for you to want or need to mock many things and greatly eliminates the excess of polymorphism. In such cases, when the constructor (which can be multiple and with several parameters if necessary) completes its execution, the object has to be ready to be used and does not need any further changes. If you need to change an object, think about creating new similar derivative objects instead of changing them. See more about this in that other answer of mine.

  • Eliminate methods and static objects that are mutable or that produce side effects by placing them as part of a "world", of a context. For example, if your payroll processing defines a mutable static variable containing the file to be processed and places other mutable static variables on the processed lines, the solution would be to create a class FolhaDePagamento and put in it an instance variable containing the file to be processed and in other instance variables the processed lines. That is, often the static variables and the methods they accompany can be grouped into a little world represented by an instance of a non-Singleton object. This tends to eliminate many cases of occurrence of static methods, in addition to improving code organization.

  • Prefer to specify objects with highly polymorphic behaviors through interfaces. This is somewhat basic object-oriented programming, but often overlooked and/or neglected.

  • Use both and method References for cases where the desired polymorphism is something simple.

  • Replace the manufacture of objects by means of static methods and constructors by Factories. An example of this is the ClienteDAO refactored above.

In general, having static methods that produce side effects, depend on external states or involve polymorphism is a sign that wrong things are happening in your project and that refactoring is necessary to eliminate them. Static methods that do not produce side effects, do not involve polymorphism and do not depend on the external state should have no reason to be mocked.

As to singletons and enums, these, as well as any other static objects, should also be immutable and not depend on any external state.

There are cases like a method that uses Collections.sort(List) that may seem complicated. This method produces side effects only in its parameter, but it does not depend on the external state nor is it polymorphic. It is an analogous case to Math.sin, and therefore should not be mocked.

Going on the question you used as an example, let’s see what’s in the code and the solution found:

public class ClasseA {

    public static final int constA = ClasseB.metodoB();


    public int metodoA(){
        System.out.println("Passei no metodo A");
        return 2;
    }
}
@RunWith(PowerMockRunner.class)
@PrepareForTest( { ClasseA.class,ClasseB.class })
public class TestesClasses {

    @Mock
    private ClasseA classeA;

    @BeforeClass
    public static void setUp(){
        PowerMockito.mockStatic(ClasseB.class);
        Mockito.mock(ClasseA.class);
    }

    @Test
    public void testando(){
        PowerMockito.when(ClasseB.metodoB()).thenReturn(5);     
        Mockito.when(classeA.metodoA()).thenReturn(1);

        int retornoA = classeA.metodoA();
        int retornoB = ClasseB.metodoB();
        System.out.println("Retorno A: "+retornoA);
        System.out.println("Retorno B: "+retornoB);
    }
}

This example is quite artificial, so you can’t tell for sure what it really should have been, but I’ll try to use it anyway. The problem here is that the metodoB() provides a constant that is not exactly a constant, since, given the need to mock it, it is concluded that its behavior may be polymorphic. The right thing would be to have an instance of ClasseB or some other class where the metodoB() was called as an instance method. As for the constA, as this would then depend on a ClasseB, then it corresponds to part of a context in which instances of ClasseA are used and created, and therefore ClasseB or would be a dependency on ClasseA or else be responsible for manufacturing ClasseA.

Now, by directly answering your questions:

If we can do it, when we should do it?

Ideally never. Do this only when there is no other way out, as in the case that the code to be tested cannot be modified or cannot be modified.

What are the cases where static method mock is beneficial?

Never. This is bad programming practice and should only be used as a last resort when the code to be tested cannot or is not feasible to modify.

When I can detect that the use of this mock is being abusive?

He is always abusive.

3

I’m going to assume that we’re not going to get into the merits of refactoring a code with static methods, because I think this would be another discussion.

Let’s have your questions.

If we can do it, when we should do it?

Normally you need a static method mock when it stores too much complexity that simply trying to bypass it turns out not to be feasible.

What are the cases where static method mock is beneficial?

Example of a class using complex static method:

class ClassComEstatico () {

    public void metodo(String variavel) {
        //algum codigo simples
        BigDecimal valor = CalcularUtils.calculoComplexo(variavel); // problema...
    }
}

The CalcularUtils.calculoComplexo(variavel) can be a real black hole, calling other Singletons, depending on other static methods, calling services by ServiceLocator... imagination is the limit. And in a test you would have to deal with all these hidden dependencies of this seemingly harmless method. In this case, it turns out to be beneficial to create a mock for it.

If the method of CalcularUtils were a simple arredondar(variavel) you wouldn’t have to worry about mocking it, given the simplicity of the method. That’s why we don’t normally worry about mocking the Utils/Helpers.

When I can detect that the use of this mock is being abusive?

I understand that this begins to occur when your setup test ends up having many mocks of static methods. This can be very confusing, because you won’t be able to easily identify which mock static matches which part of your code is being tested by unit testing. Example:

@Test
public void teste() {

    PowerMockito.mockStatic(ClassComEstaticos1.class);
    PowerMockito.mockStatic(ClassComEstaticos2.class);
    PowerMockito.mockStatic(ClassComEstaticos3.class);

    Resultado resultado = service.calcular(1, 2);
    //asserts
}

So you see the questions: Who needs the ClassComEstaticos1? And the ClassComEstaticos2? And the ClassComEstaticos3? It is the method itself calcular() that needs or is some method that the calcular calls internally? Or is it yet another class than the calcular uses? And which of these mocks does it use?

This will make it extremely difficult to maintain the tests. Abusive or not, if you need to take the test you will have no way to evade them unless you can rewrite the code and remove the static methods.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.