I have a very simple rest controller:
#RestController
public class MyController {
#Autowired
public Logger logger;
The logger dependency gets injected via the following configuration:
#Configuration
public class MyConfig {
#Bean
public Logger logger() {
return LoggerFactory.getLogger(MyController.class);
}
If I run the Spring application that contains the controller then everything works fine. However, I cannot manage to achieve this dependency injection when running my unit tests. In this case I have the following test configuration:
#Configuration
#Profile("test")
public class MyTestConfig {
#Bean
public Logger logger() {
return LoggerFactory.getLogger(MyCOntroller.class);
}
And this is the relevant part of my unit tests code:
#RunWith(MockitoJUnitRunner.class)
#ContextConfiguration(classes = MyTestConfig.class)
#ActiveProfiles("test")
public class MyContollerTest {
However the logger object does not get "autowired" in MyController (note that I do not want to mock the logger object), which results in a null pointer reference.
What am I missing?
A unit test shouldn't use any Spring configuration. You should simply instantiate your component, and inject dependencies (usually fake ones) manually.
You used field injection, which makes it a bit harder. With constructor injection, all you would need to do is
Logger logger = LoggerFactory.getLogger(MyController.class);
MyController controller = new MyController(logger);
Mockito can help injecting fake dependencies for you, though, even when using field injection, thanks to the #Mock, #Spy and #InjectMocks annotations:
#Spy
private Logger logger = LoggerFactory.getLogger(MyController.class);
#InjectMocks
private MyController controller;
#Before
public void prepare() {
MockitoAnnotations.initMocks(this);
}
That said, if I'm not mistaken, you're not using #RunWith(SpringJUnit4ClassRunner.class), so your test runner doesn't know anything about Spring, and thus doesn't create or use any Spring configuration.
Related
#SpringBootTest(classes = TestConfig.class)
class ServiceIntegTest {
}
class TestConfig {
#MockBean
RandomExecutor randomExecutor
}
I want to use RandomExecutor mock bean in ServiceIntegTest class, how to do it ?
I am not mocking methods of the bean in TestConfig class itself, because in ServiceIntegTest class there are various tests in which methods of RandomExecutor have to behave in different ways.
You do not have to #MockBean in your config, you have to do it in the test class. Then you can mock it in some test classes and use a real instance in others.
Have a look at a basic usage of #MockBean:
https://www.infoworld.com/article/3543268/junit-5-tutorial-part-2-unit-testing-spring-mvc-with-junit-5.html
You use a MockBean as you would a #Mock It just get injected into a spring context you're using for your test.
#SpringBootTest
class ServiceIntegTest {
#MockBean RandomExecutor randomExecutor;
// this service gets autowired from your actual implementation,
// but injected with the mock bean you declared above
#Autowired
YourService underTest;
#Test
void verifyValueUsed() {
final int mockedValue = 5;
when(randomExecutor.getThreadCount()).thenReturn(mockedValue);
int result = underTest.getExecutorThreads();
assertThat(result).isEqualTo(mockedValue);
}
#Test
void verifyExecutorCalled() {
underTest.performAction("argument");
verify(randomExecutor).executorMethod("argument");
}
}
I'm using Spring Boot to create a REST API and write some unit tests on my controllers.
I know that the recommended manner to inject beans in spring is the constructor injection.
But when i add the #SpringBootTest annotation to my test class, I can not inject my controller class with constructor, I find myself obliged to use #Autowired.
Have some explanation and is there another way to use constructor injection with SpringBootTest.
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class PersonControllerTest {
#LocalServerPort
private int port;
#Autowired
private PersonController controller;
#Autowired
private TestRestTemplate restTemplate;
#Test
public void greetingShouldReturnDefaultMessage() throws Exception {
assertThat(this.restTemplate.getForObject("http://localhost:" + port + "/cvtech/Persons/",
String.class)).contains("content");
}
#Test
public void contextLoads() throws Exception {
assertThat(controller).isNotNull();
}
#Test
void findAllByJob() {
}
}
For those using Kotlin, using field-injection means having to use lateinit var fields. Which is far from ideal.
It is possible to use constructor injection on SpringBoot tests however, using the #TestConstructor:
#ExtendWith(SpringExtension::class)
#TestConstructor(autowireMode = ALL)
#SpringBootTest(
classes = [MyApplication::class],
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
)
internal class MyIntegrationTest(
val beanA: BeanA,
#Qualifier("some qualified") val beanB: BeanB,
) {
...
// Tests can use the beans injected in the constructor without any problems
...
}
It's fine for your test to use field injection as the Test itself is not part of your domain; the test won't be part of your application context.
Also
You don't want to use SpringBootTest to test a controller, because that will wire ALL beans which can be way too heavy and time-consuming. Instead, you probably only want to create your controller and it's dependencies.
So your best option is to use #WebMvcTest which will only create the beans required for testing the specified controller.
#ExtendWith(SpringExtension.class)
#WebMvcTest(controllers = PersonController.class)
class PersonControllerTest {
#Autowired
private MockMvc mockMvc;
#Test
public void greetingShouldReturnDefaultMessage() throws Exception {
mockMvc.perform(get("/cvtech/Persons"))
.andExpect(status().isOk())
.andExpect(content().string(contains("content")));
}
}
Note that #WebMvcTest will search for a class annotated with #SpringBootConfiguration as it's default configuration. If it does not find it, or you want to manually specify some configuration classes, also annotate the test with #ContextConfiguration.
Also, as a sidenote, when using TestRestTemplate, you don't need to specify host and port. Just call restTemplate.getForObject("/cvtech/persons", String.class));
Same when using MockMvc or WebTestClient.
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?
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.
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.
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
}
}
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() {}
There are ways online to integrate Cucumber with Spring Boot. But I cannot find how to do so with Mockito also. If I use the Cucumber runner and annotate the steps file with ContextConfiguration and SpringBootTest, the container injects the Autowired dependencies and its all fine. The problem is that dependencies annotated with Mock, MockBean and InjectMocks dont work. Anyone knows why it doesnt work and how to make it work?
EDIT: It is possible to instantiate the bean with mock(Bean.class), instead of using the Mock annotation. But what about features like MockBean and InjectMocks?
Runner
#RunWith(Cucumber.class)
#CucumberOptions(plugin = {"pretty", "html:build/cucumber_report/"},
features = "classpath:cucumber/",
glue = {"com.whatever"},
monochrome = true,
dryRun = false)
public class CucumberTest {
}
Steps
#ContextConfiguration
#SpringBootTest
public class CucumberSteps
{
#Autowired
private Bean bean;
#InjectMocks //doesnt work
private AnotherBean anotherBean;
#MockBean //doesnt work with #Mock also
MockedBean mockedBean;
#Given("^Statement$")
public void statement() throws Throwable {
MockitoAnnotations.initMocks(this); //doesnt work with or without this line
Mockito.when(mockedBean.findByField("value"))
.thenReturn(Arrays.asList());
}
//Given-When-Then
}
Runner :
#CucumberOptions(plugin = {"pretty"},
glue = {"com.cucumber.test"},
features = "x/y/resources")
public class CucumberTest {
}
Here we will create a class with #SpringBootTest, #RunWith(SpringRunner.class) to start load beans into spring context. Now will mock the spring beans whatever we want to mack at here
#RunWith(SpringRunner.class)
#SpringBootTest
public class SpringTest {
#MockBean
private Mockedbean mockedbean;
}
Now, we need to extend the SpringBootTest annotated testclass to CucumberSteps class, then autowire the mocked bean at here, will get the instance of mocked bean(Mockedbean). We can do autowire and get instance of other spring boot beans also(TestBean)
public class CucumberSteps extends SpringTest {
#Autowired
private Mockedbean mockedbean;
#Autowired
private TestBean testBean;
}
I have a class which contains a few service activator methods as follows:
#MessageEndpoint
public class TestService {
#ServiceActivator
public void setComplete(Message<String> message){
//do stuff
}
}
In the integration flow, one of the channels call one of these methods:
#Bean
public TestService testService() {
return new TestService();
}
#Bean
public IntegrationFlow testFlow() {
return IntegrationFlows.from("testChannel")
.handle("testService", "setComplete")
.handle(logger())
.get();
}
I'm writing a unit test for this flow and using Mockito for mcoking the service activator class:
#ContextConfiguration(classes = IntegrationConfig.class)
#RunWith(SpringJUnit4ClassRunner.class)
#DirtiesContext
public class AppTest {
#Mock
private TheGateway startGateway;
#Mock
private TestService testrvice;
#Autowired
#Qualifier("testChannel")
DirectChannel testChannel;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
#Test()
public void testMessageProducerFlow() throws Exception {
Mockito.doNothing().when(startGateway).execute("test");
startGateway.execute("test");
Mockito.verify(startGateway).execute("test");
TestChannel.send(new GenericMessage<>("test"));
Mockito.verify(testService).setComplete(new GenericMessage<>("test"));
}
}
When I don't mock the TestService, it executes the flow without issues.
Any guideance on how to Mock the Service activator class would be helpful.
UPDATE:
When I mock it (as shown in snippet above), it does not call the mocked object, instead executes the actual stuff, and the last line Mockito.verify(testService)... asserts that the mock testService was never called.
First of all you misunderstood how Spring Test Framework works.
#ContextConfiguration(classes = IntegrationConfig.class) loads the config as is without any modification and start an application context based on that config.
According to the first condition your .handle("testService", "setComplete") uses testService() #Bean not #Mock
Only after the test applicationContext startup all those #Mocks and #Autowireds start working.
In other words your mocking doesn't change anything in the original IntegrationConfig.
In the Framework with use reflection to retrieve some field of the particular bean to replace it with the mock. But it isn't so easy way.
I suggest you to distinguish the Integration and Service configuration and use two different classes for production and for testing. Something like this:
The testService() #Bean must be moved from the IntegrationConfig to the new #Configuration class for production.
The TestServiceConfig may look like this:
#Bean
public TestService testService() {
return Mockito.mock(new TestService());
}
And finally your AppTest should be modified like this:
#ContextConfiguration(classes = {IntegrationConfig.class, TestServiceConfig.class})
....
#Autowired
private TestService testrvice;
That's everything is just because the application context and unit test scopes are on the different levels.