9

I have a service class which starts a thread and run some background tasks. These tasks should not be run while I'm running a unit test. The service class itself is so simple that it doesn't need to be unit tested. It is like:

@Service
public class BackgroundTaskService {
    @PostConstruct
    public void startTask() {
        // ...
    }
}

Currently I'm setting a system property to declare that a unit test is running:

@RunWith(SpringRunner.class)
@SpringBootTest
public class SomeTest {
    static {
        System.setProperty("unit_testing", "true");
    }
    @Test
    public void test() {}
}

Then I can check:

@PostConstruct
public void startTask() {
    if (System.getProperty("unit_testing") != null) {
        return;  // skip background tasks
    }
}

I'd like to know if there is a better way to do that.

JSPDeveloper01
  • 770
  • 2
  • 10
  • 25
  • What annotations you are using on the `TestClass` to load the spring context – Hemant Patel May 15 '18 at 05:56
  • You can avoid having the @PostConstruct method from being called by manually instantiating your object via ```new myObject()``` Otherwise, if you are using ```@Autowired``` your ```@PostConstruct``` method will be called. – Dennis May 15 '18 at 05:58
  • Seems like a bad idea. See this [SO](https://stackoverflow.com/questions/12159/how-should-i-unit-test-threaded-code) thread about how to test your threaded code. – Miko May 15 '18 at 06:01
  • @HemantPatel Oh sorry forgot that, added now. Just normal Spring Boot and JUnit annotations. – JSPDeveloper01 May 15 '18 at 07:09
  • I'd make it depend on the `test` profile being active instead of a custom system property. – Kayaman May 15 '18 at 07:16
  • @Mr.White The last code block in my question shows how I skip running background tasks when I notice a unit test is running. I'm not trying to avoid `@PostConstruct` methods being called. – JSPDeveloper01 May 15 '18 at 07:17

3 Answers3

12

A prettier way to handle this would be

@Service
public class BackgroundTaskService {

    @PostConstruct
    @Profile("!test")
    public void startTask() {
        // ...
    }
}

or even

@PostConstruct
public void startTask() {
    if(env.acceptsProfiles("!test")) {  // env is @Autowired Environment
        // start task
    }
}

Only if the test profile is not active, the @PostConstruct method is run. In a Spring environment you want to use the tools Spring gives you, so use a profile instead of some custom indicator.

Kayaman
  • 72,141
  • 5
  • 83
  • 121
4

Consider @MockBean. The presence of a @MockBean will remove the bean that is being mocked:

Any existing single bean of the same type defined in the context will be replaced by the mock.

https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/mock/mockito/MockBean.html

Instead of invoking the original behavior, all the methods of the mock will just return a blank or empty value by default:

By default, for all methods that return a value, a mock will return either null, a primitive/primitive wrapper value, or an empty collection, as appropriate.

https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html

This may or may not be good enough for one's use case, to replace the normal bean with a dummy bean rather than disable the bean completely.

Alexander Taylor
  • 16,574
  • 14
  • 62
  • 83
  • Nicely written. Just do not forget to comment/document (in actual test class), why do you have given "unused" mocked bean (that it is preventing original bean to do things). – Lubo Aug 17 '22 at 08:33
0

It's not a good practice to skip unit tests for even simple code, but if you really want to do that you can have a look at this thread. Instead of a property you can have a static boolean variable.

Note to community: I'm new to this forum so my reputation doesn't allow me to comment. Thanks.

Nyle Hassan
  • 188
  • 2
  • 10