#WebMvcTest vs #ContextConfiguration when testing WebSocket controllers - spring

I'm currently in the process of writing tests for my controllers in a Spring Boot project that uses WebSockets.
As information on the subject is hard to come by, my only lead is this example recommended by the docs.
Allow me to attempt to explain my forays so far, trying to understand and get my test environment set up. I'm following the context-based approach, and I'm torn between #WebMvcTest and #ContextConfiguration (which the example uses).
My motivation behind using #WebMvcTest at all was this line from the Spring Boot docs:
When testing Spring Boot applications, this [using #ContextConfiguration(classes=…​) in order to specify which Spring #Configuration to load, or using nested #Configuration classes within your test] is often not required. Spring Boot’s #*Test annotations search for your primary configuration automatically whenever you do not explicitly define one.
#WebMvcTest thus seems particularly suited to the task as it focuses only on the web layer by limiting the set of scanned beans to only those necessary (e.g. #controller) rather than spinning up a complete ApplicationContext.
The code example I'm following uses field injection to initialize channel interceptors to capture messages sent through them.
#Autowired private AbstractSubscribableChannel clientInboundChannel;
#Autowired private AbstractSubscribableChannel clientOutboundChannel;
#Autowired private AbstractSubscribableChannel brokerChannel;
As far as I can tell, these fields are what makes the presence of the TestConfig class (see code block at the end for full class definition) in the example necessary, since without it, I get an error saying no beans qualify as autowire candidates. I believe these two fields in TestConfig are the key:
#Autowired
private List<SubscribableChannel> channels;
#Autowired
private List<MessageHandler> handlers;
However, without #ContextConfiguration(classes = [WebSocketConfig::class]) (WebSocketConfig being my own WebSocket configuration file), these two fields are always null resulting in errors.
So far, this would mean that #ContextConfiguration(classes = [WebSocketConfig::class]) in combination with the presence of TestConfig are needed.
The interesting thing is that without #WebMvcTest, clientInboundChannel, clientOutboundChannel, and brokerChannel are never actually initialized. So what this leaves me with is that I need both #WebMvcTest and #ContextConfiguration, which somehow seems odd.
And with the last update to the example repo being more than two years old, I can't shake the feeling that it may be somewhat outdated.
This is what my test class (Kotlin) currently looks like. I've omitted the createRoom test case for brevity:
#WebMvcTest(controllers = [RoomController::class])
#ContextConfiguration(classes = [WebSocketConfig::class, RoomControllerTests.TestConfig::class])
class RoomControllerTests {
#Autowired
private lateinit var clientInboundChannel: AbstractSubscribableChannel
#Autowired
private lateinit var clientOutboundChannel: AbstractSubscribableChannel
#Autowired
private lateinit var brokerChannel: AbstractSubscribableChannel
private lateinit var clientOutboundChannelInterceptor: TestChannelInterceptor
private lateinit var brokerChannelInterceptor: TestChannelInterceptor
private lateinit var sessionId: String
#BeforeEach
fun setUp() {
brokerChannelInterceptor = TestChannelInterceptor()
clientOutboundChannelInterceptor = TestChannelInterceptor()
brokerChannel.addInterceptor(brokerChannelInterceptor)
clientOutboundChannel.addInterceptor(clientOutboundChannelInterceptor)
}
#Test
fun createRoom() {
// test room creation
// ...
}
#Configuration
internal class TestConfig : ApplicationListener<ContextRefreshedEvent?> {
#Autowired
private val channels: List<SubscribableChannel>? = null
#Autowired
private val handlers: List<MessageHandler>? = null
override fun onApplicationEvent(event: ContextRefreshedEvent) {
for (handler in handlers!!) {
if (handler is SimpAnnotationMethodMessageHandler) {
continue
}
for (channel in channels!!) {
channel.unsubscribe(handler)
}
}
}
}
}

The answer posted here was my first clue towards starting to understand how #WebMvcTest and #ContextConfiguration are meant to be used.
It would appear the line that is really necessary is #ContextConfiguration(classes = [WebSocketConfig::class]), since without it, the necessary configuration (my own WebSocketConfig, in this case) can't be found.
While #WebMvcTest (and test slices in general) does automatically search for the primary configuration, the configuration defined by the main application class is the one that's found since that class is the one annotated with #SpringBootApplication.
What's more, test slices such as #WebMvcTest flat out exclude #configuration classes from scanning, meaning they won't be included in the application context loaded by the test slice.
Supporting this is the fact that the test works if #WebMvcTest is replaced with #SpringBootTest — even without #ContextConfiguration(classes = [WebSocketConfig::class]) — meaning that WebSocketConfig was loaded.
To summarize, as I see it, there are 3 ways to include a non-primary configuration in the application context so that it's detected in integration tests:
Annotate the test class with #SpringBootTest (and optionally #ContextConfiguration to restrict the number of loaded beans to only those needed)
Annotate the test class with a test slice (e.g. #WebMvcTest) in conjunction with #ContextConfiguration
Annotate the test class with a test slice (e.g. #WebMvcTest) and define the configuration in the main class (i.e. the one annotated with #SpringBootApplication)

Related

Kotlin, Spring book, Mockito, #InjectMocks, Using different mocks than the ones created

I am trying to test a class like
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
#SpringBootTest(classes = [TestConfig::class])
#ExtendWith(MockitoExtension::class)
class TotalCalculatorTests {
#Mock
lateinit var jdbcRepo: JDBCRepo
#Mock
lateinit var userCache : LoadingCache<ApproverLevel, List<Approver>>
#InjectMocks
lateinit var calculator: TotalCalculator
#Test
fun totalOrder() {
// val calculator = TotalCalculator(userCache,jdbcRepo)
calculator.total(ItemA(),ItemB())
verify(jdbcRepo).getTotal()
}
}
I get an error Actually, there were zero interactions with this mock. but if I uncomment the // val calculator = TotalCalculator(userCache,jdbcRepo) it works. I assumed mockito would be using the mocks from the test class but that appears to not be true. How can I get the instance of JDBCRepo being used by the #InjectMocks?
It looks like you would like to run the full-fledged spring boot test with all the beans but in the application context you would like to "mock" some real beans and provide your own (mock-y) implementation.
If so, the usage of #Mock is wrong here. #Mock has nothing to do with spring, its a purely mockito's thing. It indeed can create a mock for you but it won't "substitute" the real implemenation with this mock implementation in the spring boot's application context.
For that purpose use #MockBean annotation instead. This is something from the spring "universe" that indeed creates a mockito driven mock under the hood, but substitutes the regular bean in the application context (or even just adds this mock implementation to the application context if the real bean doesn't even exist).
Another thing to consider is how do you get the TotalCalculator bean (although you don't directly ask this in the question).
The TotalCalculator by itself is probably a spring been that spring boot creates for you, so if you want to run a "full fledged" test you should take the instance of this bean from the application context, rather than creating an instance by yourself. Use annotation #Autowired for that purpose:
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
#SpringBootTest(classes = [TestConfig::class])
#ExtendWith(MockitoExtension::class) // you don't need this
class TotalCalculatorTests {
#MockBean
lateinit var jdbcRepo: JDBCRepo
#MockBean
lateinit var userCache : LoadingCache<ApproverLevel, List<Approver>>
#Autowired // injected by the spring test infrastructure
lateinit var calculator: TotalCalculator
#Test
fun totalOrder() {
// val calculator = TotalCalculator(userCache,jdbcRepo)
calculator.total(ItemA(),ItemB())
verify(jdbcRepo).getTotal()
}
}
Now, all this is correct if your purpose to indeed running the full microservice of spring boot and make an "integration" testing
If alternatively you just want to check the code of your calculator, this might be an overkill, you can end up using mockito and plain old unit testing. In this case however you don't need to even start the spring (read create an application context) during the test, and of course there is no need to use #SpringBootTest / #MockBean/#Autowired.
So it pretty much depends on what kind of test is really required here

How to share #MockBeans and mock methods among my tests without inheritance?

I have a base test scenario that will be used by other integration tests. This scenario includes some mock beans (#MockBean) for external integrations.
Today, I have something like this in the integration test class:
#SpringBootTest
#WebAppConfiguration
#DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
#RunWith(SpringRunner.class)
#FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class OrderIT {
And the fields and annotations to prepare my integration test:
private MockMvc mockMvc;
#Autowired
private WebApplicationContext wac;
#Autowired
private ObjectMapper mapper;
#MockBean
private SomeGateway someGateway;
#MockBean
private SomeRabbitMqService someRabbitMqService ;
#MockBean
private AnotherRabbitMqService anotherRabbitMqService;
#MockBean
private SomeIntegrationService someIntegrationService ;
#MockBean
private Clock clock;
#Before
public void setup() {
//some methods mocking each service above, preparing mockMvc, etc
}
This scenario is necessary for use the MockMvc and create the main feature in the system, my Order. This Order is created by calling a POST method in a Rest API, saving the order in a memory database.
Even this working well, I need to duplicate this block of code containing these #MockBean and some #Autowired in another tests, because the Order is the base scenario to add Products to the order, set an Address to deliver, etc. Each scenario has a different integration test but all of them needs an Order.
So, how to share the "MockBeans" and the methods that mocks them among my Integration Tests? I had really bad experiences using inheritance among the tests and I really would like to try a different approach.
I end up using the Spring profiles.
I created a configuration class annotated with #Profile("test") and created the mocked beans there. Like:
#Profile("test")
#Configuration
public class MyMockConfiguration {
#Bean
public SomeService someService() {
SomeService someService = mock(SomeService .class);
// mocked methods and results
return someService ;
}
And in the Test class:
#ActiveProfiles("test")
#SpringBootTest
#WebAppConfiguration
#RunWith(SpringRunner.class)
public class MyControllerIT {
If some integration test needs to override the current mock implementation on the profile, the test just needs to declare #MockBean on the class and proceed on the mock as usual.
I'm not decide yet if test is a good name, because for me makes more sense mock the configuration by "context". So, instead of use the generic name test on the profile name, I could use createOrder and have different configuration profiles, each one with a different name and different mocks: createOrder, createOrderWithoutProducts.
I believe #ContextConfiguration was created for this purpose: https://spring.io/blog/2011/06/21/spring-3-1-m2-testing-with-configuration-classes-and-profiles

How to inject Mocks into Spring Service

Environment :
Spring MVC 4
Junit
Mockito
Code :
Spring Service under test :
#Service("abhishekService")
public class AbhishekServiceImpl implements AbhisheskService {
#Autowired
private DaoOne daoOne;
#Autowired
private DaoTwo daoTwo;
#Autowired
private DaoThree daoThree;
#Autowired
private DaoFour daoThree;
}
Junit Test :
public class AbhishekServiceImplTest {
#Mock
private DaoOne daoOne;
#Mock
private DaoTwo daoTwo;
#Mock
private DaoThree daoThree;
#Mock
private UserDao userDao;
private AbhisheskService abhisheskService;
#Before
public void setUp(){
MockitoAnnotations.initMocks(this);
abhisheskService = new AbhishekServiceImpl();
}
}
Issue :
1)As shown in code snippet one , the class under test uses four dependencies.
2)As shown in code snippet two , in junit test case class , all 4 dependencies are mocked using #Mock
3)My question is : how these four mocked objects should be injected into test class ?
4)My class under test doesn't have constructor/setter injection but field injection using #Autowired.
5)I don't want to use #InjectMocks annotation due to its dangerous behavior
as mentioned here
Can anybody please guide on this ?
You are trying to test a class wrongly designed to test the behavior i.e. the properties are not accessible to be mocked. AbhishekServiceImpl has to provide a way to inject the mocks to the class. If you cannot access the fields then it is a clear case of wrongly designed class. Considering that the AbhishekServiceImpl is a class in a legacy code and you are trying to test the behaviour then you can use reflection to inject the mock objects as below:
DaoOne mockedDaoOne = mock(DaoOne.class);
when(mockedDaoOne.doSomething()).thenReturn("Mocked behaviour");
AbhishekService abhishekService = new AbhishekServiceImpl();
Field privateField = PrivateObject.class.getDeclaredField("daoOne");
privateField.setAccessible(true);
privateField.set(abhishekService, mockedDaoOne);
assertEquals("Mocked behaviour", abhishekService.doSomething());
Its very rare that you test behaviour of a class that you have not written yourself. Though I can imagine a use case where you have to test an external library because its author did not test it.
You can mark the junit test with #RunWith(SpringJUnit4ClassRunner.class) and then use #ContextConfiguration to define a context which instantiates the DAOs and service and wires them together.

Does #WebMvcTest require #SpringBootApplication annotation?

My goal is to migrate a Spring Boot application previously developed with Spring Boot 1.3 to the newest Spring Boot version 1.4. The application consists of several maven modules and only one of them contains class annotated with #SpringBootApplication.
One part of migration is to use #WebMvcTest annotation to efficiently test controllers, and here I get an issue.
Consider an example application from Spring Boot github page. #WebMvcTest annotation works perfectly, because, as far as I understand (after I did several tests), there is a class in the main package annotated with #SpringBootApplication. Note that I follow the same concept as shown in the example above for my own #WebMvcTest tests.
The only difference I see that in my application, controller classes are located in a separate maven module (without #SpringBootApplication annotated class), but with #Configuration and SpringBootConfiguration configurations. If I do not annotate any class with #SpringBootApplication I always get an assertion while testing controller. My assertion is the same as when SampleTestApplication class in the example above modified to have only #EnableAutoConfiguration and #SpringBootConfiguration annotations (#SpringBootApplication is not present):
getVehicleWhenRequestingTextShouldReturnMakeAndModel(sample.test.web.UserVehicleControllerTests) Time elapsed: 0.013 sec <<< FAILURE!
java.lang.AssertionError: Status expected:<200> but was:<404>
at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:54)
at org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:81)
at org.springframework.test.web.servlet.result.StatusResultMatchers$10.match(StatusResultMatchers.java:664)
at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:171)
at sample.test.web.UserVehicleControllerTests.getVehicleWhenRequestingTextShouldReturnMakeAndModel(UserVehicleControllerTests.java:68)
How should I deal with that? Should I always have class annotated with #SpringBootApplication in order to run #WebMvcTest tests?
EDIT 1: I did a small maven project with 2 modules and a minimal configuration. It is here. Now, I get NoSuchBeanDefinitionException exception for repository defined in another module. If I configure "full" #SpringBootApplication - everything is fine.
EDIT 2: I modified small test project from EDIT 1 to give an original issue. I was playing with different annotations and added #ComponentScan on configuration class, because I suspected that beans are not registered properly. However, I expect that only #Controller bean (defined in #WebMvcTest(...class)) shall be registered based on magic behind #WebMvcTest behaviour.
EDIT 3: Spring Boot project issue.
Short answer: I believe so.
Long answer:
I believe #WebMvcTest needs to find the SpringBootApplication configuration since WebMvcTest's sole purpose is to help simplify tests (SpringBootApplication would rather try to load the whole world).
In your specific case, since you don't have any in your non-test packages, I believe it also finds SampleTestConfiguration which is annotated with #ScanPackages and somehow loads every beans.
Add the following in src/main/java/sample/test
#SpringBootApplication
public class SampleTestConfiguration {
}
And change your test to this:
#RunWith(SpringRunner.class)
#WebMvcTest(MyController.class)
public class MyControllerTest {
#Autowired
private MockMvc mvc;
#MockBean
private MyService ms;
#Autowired
private ApplicationContext context;
#Test
public void getDataAndExpectOkStatus() throws Exception {
given(ms.execute("1")).willReturn(false);
mvc.perform(get("/1/data").accept(MediaType.APPLICATION_JSON_VALUE)).andExpect(status().isOk()).andExpect(content().string("false"));
}
#Test
public void testMyControllerInAppCtx() {
assertThat(context.getBean(MyController.class), is(not(nullValue())));
}
#Test
public void testNoMyAnotherControllerInAppCtx() {
try {
context.getBean(MyAnotherController.class);
fail("Bean exists");
} catch (BeansException e) {
// ok
}
}
}
#WebMvcTest finds the SpringBootApplication, then load only a limited number of beans (see documentation):
#WebMvcTest will auto-configure the Spring MVC infrastructure and
limit scanned beans to #Controller, #ControllerAdvice, #JsonComponent,
Filter, WebMvcConfigurer and HandlerMethodArgumentResolver. Regular
#Component beans will not be scanned when using this annotation.
WebMvcTest requires SpringBootApplication: WebMvcTest inherits many AutoConfiguration, so it needs SpringBoot to load them. Then it disables many other AutoConfiguration and your Controllers become easily testable.
The whole point of using WebMvcTest is when you have a SpringBootApplication and you wish to make it simpler to test by disabling all beans except Controllers. If you don't have SpringBootApplication, then why use WebMvcTest at all?
It's an old topic, but there is a solution which wasn't mentioned here.
You can create a class annotated with SpringBootApplication just in your test sources. Then, you still have a nice, multi-module structure of your project, with just one "real" SpringBootApplication.
Yes,according to the spring boot docs
The search algorithm works up from the package that contains the test until it finds a #SpringBootApplication or #SpringBootConfiguration annotated class. As long as you’ve structure your code in a sensible way your main configuration is usually found.
But after I started using #WebMvcTest,spring boot still try to load other beans, finally TypeExcludeFilter did the trick.
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = {JzYsController.class} )
public class JzYsControllerTest {
private static final String REST_V4_JZYS = "/rest/v4/JzYs";
#Autowired
private MockMvc mockMvc;
#MockBean
private JzYsService service;
#Test
public void deleteYsByMlbh() throws Exception {
Mockito.when(service.deleteYsByMlbh(Mockito.anyString())).thenReturn(Optional.of(1));
mockMvc.perform(delete(REST_V4_JZYS + "?mbbh=861FA4B0E40F5C7FECAF09C150BF3B01"))
.andExpect(status().isNoContent());
}
#SpringBootConfiguration
#ComponentScan(excludeFilters = #Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class))
public static class config{
}
}
There is one more solution. You can not use #WebMvcTest, but configure MockMvc yourself through the builder
class TestControllerTest {
private MockMvc mvc;
#BeforeEach
public void setup() {
mvc = MockMvcBuilders.standaloneSetup(new TestController())
.build();
}
#Test
void test() throws Exception {
// When
var res = mvc.perform(MockMvcRequestBuilders.get("/test/test"));
// Then
res.andExpect(status().isOk());
}
}
But this solution may entail a number of other problems, such as problems with configurations, environment property injections, etc.

Trying to generate error about Spring's #Autowired field injection for JUnit

I am working with Spring 4.0.7, and with JUnit about testing of course.
About DI Spring offers #Autowired to be used in three locations
constructor
setter
field
I always work through the two first, why never the third option?
Because I remember have read long time ago about field injection should not be used, because it has a negative consequence about Testing. It does JUnit fails.
Note: Only for Testing is the problem. To runtime or production all goes well
Objective: For demonstration/academic purposes I want generate this problem.
I have the following:
Repository
public interface PersonRepository extends JpaRepository<Person, String>{
}
A Service
#Service
#Transactional
#Profile("failure")
public class PersonFailTestServiceImpl implements PersonService {
private static final Logger logger = ...
#Autowired
private PersonRepository personRepository;
Other Service (calling or using the service shown above)
#Service
#Transactional
#Profile("failure")
public class PersonFailTestProcessImpl implements PersonProcess {
private static final Logger logger = ...
#Autowired
private PersonService personService;
How you can see the two services are based on Field Injection.
Now the testing:
How the beans are loaded
#Configuration
#ComponentScan( basePackages={"com.manuel.jordan.server.infrastructure"},
basePackageClasses={PersonProcess.class,PersonRepository.class, PersonService.class})
public class CentralConfigurationEntryPoint {
}
#ContextConfiguration(classes=CentralConfigurationEntryPoint.class)
public class CentralTestConfigurationEntryPoint {
}
Now the two testing classes
#Transactional
#RunWith(SpringJUnit4ClassRunner.class)
#ActiveProfiles({"development","failure"})
public class PersonServiceImplDevelopmentFailureTest extends CentralTestConfigurationEntryPoint {
#Autowired
private PersonService personService;
#Test
public void savePerson01(){
Person person01 = PersonFactory.createPerson01();
personService.save(person01);
personService.printPerson(personService.findOne("1"));
}
#Transactional
#RunWith(SpringJUnit4ClassRunner.class)
#ActiveProfiles({"development","failure"})
public class PersonProcessImplDevelopmentFailureTest extends CentralTestConfigurationEntryPoint{
#Autowired
private PersonProcess personProcess;
Well all the testing methods pass, all green. I don't know if I am missing something or through Spring 4 the problem has been fixed
If this was your premise or problem
Because I remember have read long time ago about field injection
should not be used, because it has a negative consequence about
Testing. It does JUnit fails.
then you thought wrong. There is nothing inherently wrong with using field injection, definitely nothing that would cause JUnit tests to fail in and of itself. If a bean exists, Spring will be able to inject it whether it's in a constructor, a setter method, or a field.
Since you've activated your failure profile, your PersonFailTestServiceImpl bean will be found.
I think I can help. The example code you've posted here is a good example of a system / integration test, not a UNIT test.
If you were UNIT testing PersonFailTestProcessImpl, you would have to set the personRepository dependency yourself through code. But it is private, so how do you do this? You cannot use a constructor or setter since none is provided. This is what is meant by 'hard to unit test'.
Java 5+ provides a way to set private variables like this via reflection (the so-called privileged accessor). Basically, you obtain the class, get the declared field, call its setAccessible method, then you can set its value directly. There are libraries that will do these steps for you, but the point is that this is a pain compared to X.setSomething();
So there is nothing that 'makes jUnit fails' by using #Autowired on a private field. But building an object model without constructors or setters for establishing dependencies is unnecessarily constraining.

Resources