I'm trying to write integration tests for a Spring Boot 2 Application.
One test should test updating a value via REST.
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureTestEntityManager
#Transactional
public class TenantEndpointIT {
#Autowired
private TestRestTemplate template;
#Autowired
private TestEntityManager entityManager;
#Test
public void nok_updateValueForbidden() {
}
}
Now, I thought the cleanest way was to create the value with the TestEntityManager in the #Before method, then test the REST endpoint in the actual test.
But the service called by the REST Endpoint is annotated with Spring Caching annotations. So the test fails if I do that. I could use the service directly or make a second REST call. That creates problems with other tests using the same Value, because even if the DB is rolled-back, the cache seems to contain the value. (Now I'm using #DirtiesContext).
My question is, how do you correctly integration test services with #Cachable?
Is there a way to get the Cache and explicitly put/remove?
I tried autowiring a CacheManager, but it won't find one and fails.
If you add #AutoConfigureCache on your test, it will override whatever cache strategies you've defined in your app by a CacheManager that noops. That's pretty useful if you want to make sure that cache doesn't interfere with your tests.
Related
First of all, its important to specify that the app is already deployed and our spring security configuration works. If an unauthenticated user tries to access to any endpoint of the API it returns 401 Unauthorized.
Following, this sample example, I want to test a Controller with #WebMvcTest and spring security
#WebMvcTest(EmployeeController.class)
#Import(SecurityConfig.class)
class EmployeeControllerTest {
#Autowired
private WebApplicationContext ctx;
protected MockMvc mvc;
#MockBean
private EmployeeService service;
#BeforeEach
public void setUp() {
this.mvc = MockMvcBuilders
.webAppContextSetup(ctx)
.apply(springSecurity())
.build();
}
#Test
void unauthorized_role_should_return_401() {
mvc.perform(get("/employees/1").accept(MediaType.APPLICATION_JSON)
.with(SecurityMockMvcRequestPostProcessors.user("manager").roles("UNKNOWN")))
.andExpect(status().isUnauthorized())
}
}
This code works, but I don't understand why I need to import the SecurityConfig class to the test class. Indeed, if I remove the #Import, mockMvc returns 200. But, every sample project that I found over Github simply use #WebMvcTest even if the project has a SecurityConfig class
There is no need to configure extra thing in order to enable spring security when using #WebMvcTest as #WebMvcTest will enable it automatically.
But if you customise the spring-security by creating your own beans , you still have to define these customised beans for the tests for the customisation to work. #Import is one of the way to define these beans. By consolidating their configuration into a SecurityConfig , it can keep your test cases DRY because if a test case need to test with the security stuff , it just need to import this SecurityConfig rather repeatedly do the same configuration for each test.
So it is rather a code design decision which is an application specific and hence it is normal that not every project will have the same kind of setup.
In a spring boot integration test annotated with #SpringBootTest and ran with #RunWith(SpringRunner.class) I can drop real http post calls to my rest controller via #Autowired TestRestTemplate restTemplate and restTemplate.postForEntity(...)
This works fine, just at the end of the controller -> service -> restclient chain I have a rest client bean, which is calling a 3rd party rest endpoint using RestTemplates inside, so I have to mock this endpoint. I found this lib com.github.tomakehurst.wiremock.client.WireMock, which can be used for this, but was wondering whether there is not a nice spring boot way like for example the way to test a rest client with #RestClientTestto achieve this. I tried to mock the MockRestServiceServer so that I can write expectations and responses on it, but it does not seem to get it. The rest templates inside my rest client are always created as real, thus my call to the 3rd party endpoint fails.
You can inject the mock in the #Before using ReflectionTestUtils
#Inject
private Service service;
#Mock
private RestClient restClient;
#Before
public void setup() {
ReflectionTestUtils.setField(service, "restClient", restClient);
}
I am using test annotation introduced in spring-boot 1.4.3 for my integration tests
#RunWith(SpringRunner.class)
#SpringBootTest
public class MyServiceIT { }
According to documentation, test context is cached and reused to speed up integration tests. This behavior is what I want since it takes significant amount of time to initialize application context. My failsafe plugin is configured with
<forkCount>1</forkCount>
<reuseForks>true</reuseForks>
to allow integration tests to run in the same process to take advantage of application context caching.
Recently, I wrote a integration test used #MockBean annotation to mock behavior for some beans.
#RunWith(SpringRunner.class)
#SpringBootTest
public class AnotherServiceIT {
#MockBean
SomeService service1
}
While the test runs fine on it's own, when running through maven verify, multiple integration tests fails with the error message
javax.naming.NamingException: Another resource already exists with
name dataSource - pick a different name
If I skip this particular test with JUnit #Ignore annotation, everything goes back to normal.
This behavior seems to indicate that using #MockBean changes the caching behavior, and each test attempts to create its own datasource. I should also mention that I am using an AtomikosDataSourceBean created through XADataSourceAutoConfiguration.
How can I overcome this issue so my integration test can still use cached context and use #MockBean at the same time?
Hmm, does SomeService relate to your Datasource in any way?
Because your context is cached and #MockBean does the following:
used to add mocks to a Spring ApplicationContext ... Any existing single bean of the same type defined in the context will be replaced by the mock,
and
If there is more than one bean of the requested type, qualifier metadata must be specified at field level:
#RunWith(SpringRunner.class)
public class ExampleTests {
#MockBean
#Qualifier("example")
private ExampleService service;
Edit:
So if your SomeService is an implementation of a DataSource try adding a Qualifier. If SomeService has a DataSource in it, and you need to access some methods in it, you could try to use #Mock and specify the any objects that need to be returned either through their own mock or autowire.
#Mock
SomeService someService;
#Mock
SomeDependency mockDependency;
#Autowired
OtherDependency realDependency;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
doReturn(mockDependency).when(someService).getSomeDependency();
doReturn(realDependency).when(someService).getOtherDependency();
}
I'm using Spring Rest Docs to generate documentation for my REST services. This involves running unit(strictly integration) tests that run against a live Spring Boot Container that is kicked off by the test. The test class looks like this:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = MySpringConfiguration.class)
#WebAppConfiguration
public class ApiDocumentation {
private MockMvc mockMvc;
#Rule
public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation("target/generated-snippets");
#Autowired
private WebApplicationContext context;
#Autowired
private ObjectMapper objectMapper;
#Before
public void setUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.build();
}
#Test
public void testSomething() throws Exception {
}
}
The application uses JPA with EclipseLink for the EntityManager implementation.
When I run the test standalone in my IDE or as the only test present when I run a Maven build using the maven-surefire-plugin everything works fine.
However it's not the only test I want to run in the suite. As soon as I run other tests in the suite I come across the issue mentioned here, namely
"Spring's agent does not initialize the persistence context until the application accesses the Spring context. If the application has already triggered the loading of the persistent class before accessing the Spring context, weaving will not occur."
and get errors like this:
Exception Description: The method [_persistence_set_someField_vh] or [_persistence_get_someField_vh] is not defined in the object [mypackage.MyEntity].
So what do people normally do to get around this ? Run SpringBootTest classes in a different module to unit tests that access entities ?
As far as I concerned problem caused by dynamic weaving, if you make it static it should work proper. Possibly it could help you
Another solution could be to disable dynamic weaving in that particular test using eclipselink.weaving JPA property.
See this question and its answers: #SpringBootTest interferes with EclipseLink dynamic weaving
I have a spring managed application in which I like for my service layer to be mocked. So I created a Spring Application Java Config and returned a mock of the actual service.
For e.g,
#Bean
#Profile("resource")
public MyService mockService() {
return mock(MyService.class)
}
And then the MyService goes as
class MyService {
#Autowired
private MyDao dao;
}
When Spring creates bean of name "mockService", it also tries to Autowire MyDao on the mock ? This in my opinion defeats the purpose of mocking. Is this the expected behaviour, whats the workaround ?
So bottomline, its best practice to code to interfaces rather than concrete classes particularly if you are wanting to write focused tests on specific layers.
Well, I suppose you're trying to execute an integration test, but using some mocks, doing something like this:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = {Application.class, YourMockConfigurationClass.class})
#WebIntegrationTest
public class MyIntegrationTest {
...
}
In this case, you can create a mock of your service, and tell spring that this service instance has priority:
#Bean
#Primary
public MyService mockService() {
return mock(MyService.class)
}
Doing this, whenever Spring has to inject an unqualified MyService instance, it always chooses your mock. But it doesn't prevent Spring to create the original service.
So inside Spring context there will be two instances of MyService, the mock instance and the original implementation. But using #Primary you're telling Spring that your mock has priority.
On the other hand, in order to prevent Spring to load any mock in your environments (dev, test, etc...), you should annotate your mock configuration class either with a testing #Profile or a custom annotation (like #MockBean) and then configure your component scan strategy to not load this class (#ComponentScan(excludeFilters=#Filter(MockBean.class)))