JUnit/Mockito: ArgumentCaptor running before the JUnit test executes? - spring

I have JUnit test such as follows:
#Autowired
MyService myservice;
#Before
public void init() {
myservice.doStuff(new MyObj());
}
#Test
public void test() {
ArgumentCaptor<MyObj> captor = ArgumentCaptor.forClass(MyObj.class);
myservice.doStuff(new MyObj());
verify(myservice, atLeastOnce()).doStuff(captor.capture());
captor.getAllValues(); //this returns 2 - one for the #Before and one for right above
}
As mentioned in the commented code, the captor captures both invocation even though the captor is created after the #Before. Why is this and how can I capture only invocation within the test ?

Something looks wrong in this flow, I'll explain and hopefully this will lead you to the solution:
First of all, ArgumentCaptor is used only with mocks. So myservice has to be a mock,
I was expecting to see something like #MockBean on it or maybe #Mock if you're running a plain mockito test without spring.
Now assuming it is a mock, why would you call a method doStuff on the mock in #Before phase. I can understand if you want to setup some global expectations applicable to all the tests in the class in case you have many, but this seems suspicious to me.
Now, when you use the verify what you actually say is:
Mockito, make sure that my mock myservice called method doStuff (at least once) and for futher verification I would like to "capture" the arguments that were passed to the mock while the method invocation (s).
With this approach its understandable that argument capture gets all the information about all the invocations, and I believe its by design.
To make a point clear, #Before method has nothing to do with mockito, its a purely JUnit hook, so by the time it is called by JUnit framework, all mocks are already inialized and are ready to "record" all the relevant information about everything that the code will do to them (method invocations I mean).

Related

Mocking repository / filling db BEFORE container start for app-startup integration test

I'd like to write integration test to verify proper function of #EventListener(ApplicationReadyEvent.class) annnotated method, but I need to setup the data to be found by some repository. The #SQL is executed only before #Test method, ie. after the ApplicationReadyEvent was processed., #MockBean followed by Mockito.when in #Before method is the same, it's reached only after #SpringBootTest finished initialization, namely after ApplicationReadyEvent was processed.
I also tried to provide #TestConfiguration with bean defined as:
#Bean
SomeRepository someRepository() {
SomeRepository someRepository = Mockito.mock(SomeRepository.class);
Mockito.when(someRepository.findById("value")).thenReturn(...);
return someRepository;
}
but this is somewhat insufficient, as the #SpringBootTest still finds actual implementation of this repository (even if I add #AutoConfigureMockMvc) and run crashes with this conflict.
How can I prepare the mocked repository or actual data in test db for this scenario?

Multiple testfiles and MockRestServiceServer, expecting calls from other testfile

I've build a service with two endpoints, and I want to cover both endpoints with integration tests. To prevent these integrationtests from reaching other services, I'm using the MockRestServiceServer class to mock calls and responses to other HTTP services.
TestOperationA:
#RunWith(SpringRunner.class)
#SpringBootTest
#ActiveProfiles({"integration"})
#ComponentScan(basePackages = "nl.xyz")
public class OperationAIntegrationTest {
MockRestServiceServer mockServer;
#Autowired
RestTemplate restTemplate;
#Autowired
OperationA operationA;
#Before
public void setup() {
this.mockServer = MockRestServiceServer.bindTo(restTemplate).bufferContent().ignoreExpectOrder(true).build();
this.mockServer.reset();
}
#After
public void finish() {
// Verify all method calls are run after the testcase.
this.mockServer.verify();
this.mockServer.reset();
}
And then testcases contain stuff like:
this.mockServer.expect(requestTo(ENDPOINT_OAUTH))
.andExpect(method(HttpMethod.POST))
.andRespond(withSuccess(objectMapper.writeValueAsString(oAuthToken), MediaType.APPLICATION_JSON));
I do the same for OperationBIntegrationTest. This includes the binding to restTemplate!
Now the problem is that if I run all testcases seperately, everything succeeds. If I run all the testcases from OperationA ór OperationB, they all succeed. But when I run all the testcases so the integrationtests from OperationA and OperationB are executed in sequence, the testcases from OperationB fail. Even though I see that Spring Boot gets started anew when the testing framework jumps to the second testing file.
I'm thinking that the MockRestServiceServer does not get cleaned up or I'm doing something wrong with the binding to RestTemplate. I tried the .reset() and .verify() combinations by placing them in #Before and #After, but with no effect. Does anybody know why this is happening?
Apparently, some stuff was happening in the background causing certain variables and methods not being updated anymore since a previous test already updated it. When I don't dirty the application context (by having more MockBean's for example) then everything is allright.
So the ones adjusting the values in the background should be marked dirty.

How to perform Spring Rest controller testing using same test class with different data?

I am currently trying to write very simple integration tests for my Spring REST controllers.
Lets say my test class looks something like this:
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
public class RealNewTest2 {
#Autowired
private MockMvc mvc;
#Test
public void test() throws Exception {
mvc.perform(
get(GET_URL).
with(httpBasic("user","pass"))).
andExpect(status().isOk());
System.out.println("Test done.");
}
}
I want to perform very basic test case that would test all the calls(GET,POST,PUT,DELETE) etc. All of my REST controllers are very alike. The goal that I am thinking is that I would have test data for all controllers like the JSON object that it uses when doing PUT test and then it would have the URL/Mapping that the controller uses. All of my controllers Mappings are the same expect the last part for example mysite/accounts and mysite/countries.
So is there any way that i could write one test case that would perform all of these REST calls and then just run it again with different url and JSON object so i wouldn't have to write so many test cases since they are only VERY basic tests and are basically exactly the same expect for the JSON object and REST URL.
Create a class called something like AbstractControllerTest and put the shared behavior you want in it. Then your controller test classes can extend from it. You can customize the parameters (like the URL) of the test class through the constructor.

Multiple tests with autowired MockHttpServletRequest not working?

I use an #Autowired MockHttpServletRequest in some of my Spring tests. TestNG is used as testing framework. If I only have one test method in the class this works fine. However, if there are multiple test methods, only the first run test uses my MockHttpServletRequest. Let me illustrate with an example:
#WebAppConfiguration
#ContextConfiguration({"classpath:applicationContext.xml"})
public class FooTest extends AbstractTestNGSpringContextTests {
#Autowired
private MockHttpServletRequest servletRequest;
#Test
public void test1() {
assertEquals(((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest(), servletRequest);
}
#Test
public void test2() {
assertEquals(((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest(), servletRequest);
}
}
In this example, test1() passes, but test2() fails! If you run the test methods individually, they both pass. Why does one test fail if they are run together?
I tried to dig in the code, there seems to be some kind of reset of the request attributes after a test method have run, but I didn't find a way to turn it off. My Spring version is 3.2.8.RELEASE.
UPDATE: This has been fixed in Spring Framework 3.2.9, 4.0.4, and 4.1. See SPR-11626 for details.
Well, my friend... you have discovered a bug in the Spring TestContext Framework.
The reason for this behavior is that ServletTestExecutionListener resets the request attributes after each test method, but DependencyInjectionTestExecutionListener does not re-inject dependencies before each test method (by default). When the second test method is executed, the servletRequest field is still referencing the MockHttpServletRequest that was created for the previous test method; whereas, ServletTestExecutionListener creates a new instance of MockHttpServletRequest for each test method and sets it in the request attributes. Thus, the injected request and the one stored in the RequestContextHolder are only the same for the first test method that executes in TestNG.
Since I am the author of this code, I have to personally apologize, but... I'll make sure it gets fixed ASAP. See SPR-11626 for details on the status of the fix. ;)
Note: this bug only applies to TestNG tests; this does not apply to JUnit tests.
As a work-around, you can annotate the affected test methods with #DirtiesContext (or annotate your test class with #DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)). This will allow your tests to pass as you expect.
The use of #DirtiesContext will make Spring close your test ApplicationContext after each test method, and this will likely have a negative impact on the speed of your tests; however, as of Spring 3.2.8 and 4.0.3, this is the only non-custom solution.
Having said that, the following is a much more efficient work-around. Just define this custom TestExecutionListener in your project:
public class AlwaysReinjectDependenciesTestExecutionListener extends AbstractTestExecutionListener {
public void afterTestMethod(TestContext testContext) throws Exception {
testContext.setAttribute(DependencyInjectionTestExecutionListener.REINJECT_DEPENDENCIES_ATTRIBUTE, Boolean.TRUE);
}
}
And then annotate your test class like this:
#TestExecutionListeners(AlwaysReinjectDependenciesTestExecutionListener.class)
That should clear up any issues and keep your test suite running quickly.
Regards,
Sam

Using Spring Framework's #Autowired for instantiating and injecting SUT (System Under Test) in the test fixture

I have seen developers using Spring's #Autowired feature making Spring framework responsible for instantiating and injecting SUT (System Under Test) or CUT (Class Under Test) in the test class or fixture. The following is the snippet showing #Autowired being used in the test fixture:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.ExpectedException;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.springframework.test.context.ContextConfiguration;
interface BackingStore
{
//Some methods
}
class Logger
{
public Logger(BackingStore backingStore)
{
//Capture input parameter into member variables
}
public void Log(String message)
{
//Some logic
}
}
#ContextConfiguration(locations = { "classpath:someconfig.xml" })
public class LoggerTests extends AbstractTestNGSpringContextTests
{
#Autowired
private Logger _logger;
#Test
#ExpectedException(NullPointerException)
public void Log_WhenPassedNull_ShouldThrowException()
{
_logger.Log(null);
}
}
All the dependencies (recursively) required by the SUT are specified as part of the Spring configuration XML file. I do not like this approach. I like all the test (unit or integration) to read like a story (I heard Kent Beck saying the same thing :)). I like instantiating SUT/CUT in the test case itself even though if it is complex. This gives a clear picture about the test case.
I have few concerns regarding #Autowired or any auto injection mechanism being used for injecting SUT in the test fixture:
It reduces test code readability. #Autowire appears like magic. Arrange of AAA (Arrange-Act-Assert) moves to XML file from test code.
It reduces test code maintainability. This is because of #2.
Could not effectively verify constructor of SUT/CUT throwing exception in exceptional cases. I am not 100% sure about this. I do not know if Spring framework has an answer for this.
It seems overkill for unit or integration tests.
I ask experts for 2 cents on this subject.
Thanks.
It only reduces test code readability if you dont know where to look for the #Autowired objects. I would advise using SpringJunitTestRunner that defines the test application context at the top of the unit test class.
Using dependency injection in your test cases allows you to easily test with different objects.
E.g. If your code required a 3rd party service, you could use dependency injection (e.g. Autowiring a Spring bean) to inject a mocked service for your unit tests and the real service for the application.
So for this reason it definitley doesnt decrease the test code maintainability, as it is really encouraging loose coupling between the code youre testing and any external objects.
It may be overkill to inject all objects in such a way, but it is definitely not overkill for unit tests/integration tests in general.

Resources