2
votes

Assume, that I have a test configuration with several Spring beans, that are actually mocked and I want to specify the behavior of those mocks inside JUnit test suite.

@Profile("TestProfile")
@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages = {
        "some.cool.package.*"})
public class IntegrationTestConfiguration {

    @Bean
    @Primary
    public Cool cool() {
        return Mockito.mock(Cool.class);
    }
}

// ...

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
@ActiveProfiles("TestProfile")
public class CoolIntegrationTest {

    private final Cool cool;

    @Autowired
    public CoolIntegrationTest(Cool cool) {
        this.cool = cool;
    }

    @Test
    public void testCoolBehavior {
        when(cool.calculateSomeCoolStuff()).thenReturn(42);
        // etc
    }
}

If I run this test I will get:

java.lang.Exception: Test class should have exactly one public zero-argument constructor

I know the workaround like use Autowired fields in tests, but I wonder if there a way to use Autowired annotation in JUnit tests?

4

4 Answers

1
votes

It's not the autowiring that's the problem, it's the no-arg constructor. JUnit test classes should have a single, no argument constructor. To achieve what you are attempting to do, you should do the following:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
@ActiveProfiles("TestProfile")
@ContextConfiguration(classes = {IntegrationTestConfiguration.class})
public class CoolIntegrationTest {

    @Autowired
    private final Cool cool;

    @Test
    public void testCoolBehavior {
        when(cool.calculateSomeCoolStuff()).thenReturn(42);
        // etc
    }
}

The contextConfiguration annotation tells spring which config to use for the test, and autowiring the field instead of the constructor will allow you to test your spring bean.

1
votes

To run a test using Spring you have to add @RunWith(SpringRunner.class) and make sure that your class is added to the classpath. There are a few ways to do it. I.e. Add class to MVC configuration @WebMvcTest({Class1.class, Class2.class}) or use @ContextConfiguration.

But I see your code, I suppose that it would be easier just use @Mock or @MockBean to mock your beans. It will be much easier.

0
votes

JUnit requires the Test case to have a no-arg constructor, so, since you don't have one, the exception happens before the wiring process.

So Constructor-Autowiring just doesn't work in this case.

So what to do?

There are many approaches:

The easiest one (since you have spring) is taking advantage of @MockBean annotation:

@RunWith(SpringRunner.class)
@SpringBootTest
 ....
class MyTest {

   @MockBean
   private Cool cool;

   @Test
   void testMe() {
      assert(cool!= null); // its a mock actually
   }
}
-1
votes

Besides args constructor you need to have additional one no-args constructor. Try add it and check if this exception still occurcs.

@Autowired
public CoolIntegrationTest(Cool cool) {
        this.cool = cool;
    }

public CoolIntegrationTest() {}