We'are imlementing part of our security at service layer, so I add #PreAuthorize annotation to some methods of MyService.
At MyServiceSecurityTest I want to test only security role-permission matrix, without any business logic. For that reason I have to mock MyService. the problem is that both Mockito and Spring security use CGLIB proxies, and my service is not enhanced with #PreAuthorize after Mockito.mock(MyService.class).
Is there any way to mock service and preserve #PreAuthorize logic?
Example:
#Service
public class MyService implements IMyService {
#Override
#PreAuthorize("hasAuthority('SYSOP')")
public void someMethod(ComplexDTO dto) {
// lots of logic and dependencies, require lots of stubbing.
}
}
In order to avoid initialization of all dependencies of MyService#someMethod and building ComplexDTO at MyServiceSecurityTest I want to mock MyServiceSecurityTest but preserve #PreAuthorize checks.
You need to do integration tests and not unit tests. In general, you do not see mock classes in integration tests, at least you would not mock the class you are testing, in this I case I guess its the MyService class.
Setting up integration tests involves reading up on, but the short example below should get you on the right path
#RunWith(SpringRunner.class)
#SpringBootTest
#ActiveProfiles("myProfile")
public class MyServiceIT {
private final Logger logger = LoggerFactory.getLogger(getClass());
#Autowired
private TestRestTemplate restTemplate;
#Test
public void testMyService() {
logger.info("testMyService");
//user TestRestTemplate to call your service.
}
}
EDIT: In this integration test, Spring boots up normally. That means all the annotations for security are processed and all the beans it needs to create are created and properly injected. One thing you may have to control is the Spring profile.... that can be done with the #ActiveProfiles("myProfile") annotation, which I just added to the example.
Related
I have implemented Micrometer Prometheus counter in my service by injecting MeterRegistry and incrementing the count as shown below, and I have written a test case as well, but when I am running the test case, I am getting:
"java.lang.NullPointerException: Cannot invoke
"io.micrometer.core.instrument.MeterRegistry.counter(String,
String[])" because "this.meterRegistry" is null".
Service file:
#Autowired
private MeterRegistry meterRegistry;
public void counterIncrement() {
meterRegistry.counter("test_count").increment();
}
Test case file:
#MockBean
private MeterRegistry registry;
#Test
void testCounter() {
// invoking counterIncrement();
}
How do you create your class under test?
Since the registry is never instantiated, something seems up with how you setup your test.
Check that you are using the #MockBean in the correct way. This will replace the bean in the application context and if you do not spin up a spring context in your test, it will not work. See this post for more info.
A different approach would be to use #Mock and inject the registry in the constructor, example:
#ExtendWith(MockitoExtension.class)
public class MyServiceTest {
#Mock
private MeterRegistry registry;
private MyService myService;
#BeforeEach
void setup() {
myService = new MyService(registry);
}
#Test
void testCounter() {
var counter = mock(Counter.class);
given(registry.counter(any(String.class))).willReturn(counter);
myService.counterIncrement();
}
You can test metrics without Mockito using SimpleMeterRegistry
#Test
void testCounter() {
var meterRegistry = new SimpleMeterRegistry();
Metrics.addRegistry(meterRegistry);
// invoke counterIncrement();
var actual = meterRegistry.counter("test_count").count();
assertEquals(1.0d, actual);
}
Depending on which junit version you are using you need to add the annotation to your test class. Junit 5: #ExtendWith(MockitoExtension.class) or for Junit 4: #RunWith(MockitoJUnitRunner.class)
Depending on the test and the service there are several ways to deal with the missing MeterRegistry.
If you use a spring context in your test, try to use a test configuration to create the MeterRegistry bean.
If your test uses some Mock framework, you could mock the MeterRegistry as suggested by by #Hans-Christian.
If you simply make the member meterRegistry non-private. You could set it to a SimpleMeterRegistry in some setup method, anotated with #BeforeEach as suggested by #checketts in the comments.
If mocking the meter registry gets complicated, you could easily build and use some factory that provides the registry and mock this factory. A very easy factory will do, e.g. a spring #Component with an autowired MeterRegistry and some public getter for the factory.
You could use the factory method pattern as described in wikipedia to get the MeterRegistry, overwrite the factory method in a subclass of your service and use this subclass in the test. (Note that the gang of four did use a static factory method, you'll need a non-static method.)
I favour solution 3 but would use solution 1 whenever appropriate. I've added solutions 4 and 5 just because there might be some additional reasons and special cases that make these solutions a good choice. If so, I prefer 4 over 5.
Am developing MicroServices in springBoot. Am writing unit test for Service and DAO layer. When I use #SpringBootTest it starting application on build. But It should not start application
when I run unit test. I used #RunWith(SpringRunner.class), But am unable to #Autowired class instance in junit class. How can I configure junit test class that should not start application and how to #Autowired class instance in junit class.
Use MockitoJUnitRunner for JUnit5 testing if you don't want to start complete application.
Any Service, Repository and Interface can be mocked by #Mock annotation.
#InjectMocks is used over the object of Class that needs to be tested.
Here's an example to this.
#RunWith(MockitoJUnitRunner.class)
#SpringBootTest
public class AServiceTest {
#InjectMocks
AService aService;
#Mock
ARepository aRepository;
#Mock
UserService userService;
#Before
public void setUp() {
// MockitoAnnotations.initMocks(this);
// anything needs to be done before each test.
}
#Test
public void loginTest() {
Mockito.when(aRepository.findByUsername(ArgumentMatchers.anyString())).thenReturn(Optional.empty());
String result = aService.login("test");
assertEquals("false", result);
}
With Spring Boot you can start a sliced version of your application for your tests. This will create a Spring Context that only contains a subset of your beans that are relevant e.g. only for your web layer (controllers, filters, converters, etc.): #WebMvcTest.
There is a similar annotation that can help you test your DAOs as it only populates JPA and database relevant beans (e.g. EntitiyManager, Datasource, etc.): #DataJpaTest.
If you want to autowire a bean that is not part of the Spring Test Context that gets created by the annotatiosn above, you can use a #TestConfiguration to manually add any beans you like to the test context
#WebMvcTest(PublicController.class)
class PublicControllerTest {
#Autowired
private MockMvc mockMvc;
#TestConfiguration
static class TestConfig {
#Bean
public EntityManager entityManager() {
return mock(EntityManager.class);
}
#Bean
public MeterRegistry meterRegistry() {
return new SimpleMeterRegistry();
}
}
}
Depending your test setup, if you don't want to autowire a mock but the "real thing", You could simply annotate your test class to include exactly the classes you need (plus their transitive dependencies if necessary)
For example :
#SpringJUnitConfig({ SimpleMeterRegistry.class })
or
#SpringJUnitConfig
#Import({ SimpleMeterRegistry.class })
or
#SpringJUnitConfig
#ContextConfiguration(classes = { SimpleMeterRegistry.class })
See working JUnit5 based samples in here Spring Boot Web Data JDBC allin .
Here is my idea, I try to test my Restful controller with MockMvc
mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
mockMvc.perform(post(...).param(..))
.andExpect(...);
The API I call fires a rabbitmq message. But I don't want to test Amqp in the test. Instead, I create a mock producer like this:
#Mock
private AmqpProducer producer
I want to inject this producer into the spring context, so I can capture the method call producer.sendMessage and test the message.
To mock a bean in the Spring Boot contexte you cannot use directly #Mock.
It will create a mock for AmqpProducer but not which used by your container.
With Spring, the classical way to do that is annotating your test class with a specific context configuration class or file (#ContextConfiguration(...)) that provides the mock.
With Spring Boot, it is simpler : annotating your class with #WebMvcTest
and your field to mock with #MockBean is enough to mock the bean in the container (Spring guide).
Note that #WebMvcTest with a specified controller class specified in the annotation value will instantiate the specified controller and also all its direct dependencies declared. So you should mock all of them and not only which one that interests you in your unit test.
So it should look like :
#RunWith(SpringRunner.class)
#WebMvcTest(MyController.class)
public class WebMockTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private AmqpProducer producer;
#Test
public void foo() throws Exception {
this.mockMvc.perform(...);
verify(producer).sendMessage(expected);
}
}
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
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.