I have the following test class:
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = TestClass.Config.class)
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class TestClass {
#Autowired
private MyClass myClass;
#Test
public void test1() {
myClass.run(); // this is mock
}
#Test
public void test2() {
myClass.run(); // Need real context
}
#Configuration
static class Config {
#Bean
MyClass myClass() {
return mock(MyClass.class);
}
}
}
How can I replace test context to the real context in different test methods?
It is another layer test. It rule of thumb to keep them separated.
You should create separate test class with real context. It should be named TestClassIntegrationTest(for example). Real context tests can be really slow and sametimes they should be ran as last check before commit.
#ContextConfiguration annotation can be defined only on Classes. Here is its definition:
#Target(ElementType.TYPE)
...
public #interface ContextConfiguration {...}
Hence its impossible to define different contexts for different tests in the same class.
In addition, Spring Application context loads before the actual test method runs so you can't really influence it from within the method.
So the best would be indeed running two different test cases (different files)
Related
I've a TestClass with two Tests foo() and bar(), foo uses the original bean serviceObject, while bar uses a mockedBean: so always one test fails. is there any way to mock my bean only for bar()
#RunWith(SpringRunner.class)
#SpringBootTest
pubic class TestClass{
#MockBean
private ServiceObject serviceObject;
#Test
public void foo(){
//do smt test with non mocked bean serviceObject
}
#Test
public void bar(){
//do smt with mockedBean
}
}
I would split up the tests into two different classes, as they obviously test something completely different.
One (foo()) probably tests the behaviour of the ServiceBean implementation, the other (bar()) tests something that uses a ServiceBean instance.
I assume you can create one instance with #Autowired annotation in class scope and mock it in method scope.
Just use the object that you need to use in any given test.
Here is some example code:
#RunWith(SpringRunner.class)
#SpringBootTest
public class TestClass {
#MockBean
private ServiceObject mockServiceObject;
#Test
public void foo(){
ServiceObject blam = new ServiceObject();
// Use blam in this test
// do something test with non mocked bean serviceObject
}
#Test
public void bar(){
// do something with mockedBean
// use mockServiceObject in this test
}
}
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() {}
We have a Spring based JUnit test class which is utilizing an inner test context configuration class
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = ServiceTest.Config.class)
public class ServiceTest {
#Test
public void someTest() {
...
#Configuration
#PropertySource(value = { "classpath:application.properties" })
#ComponentScan({ "..." })
public static class Config {
...
New functionalities have been recently introduced to the Service class, for which the concerned tests should be added to ServiceTest. However these would also require a different test context configuration class to be created (the internals of the existing Config class are fairly complex and change it to serve both old and new tests seems to be be extremely difficult if possible at all)
Is there a way to achieve that certain test methods in one test class would use one config class and other methods would use another? #ContextConfiguration seems to be applicable only on class level, so solution could be to create another test class for the new tests which would utilize its own context configuration class; but it would mean that the same Service class is being covered via two different test classes
With Aaron's suggestion of manually building the context I couldn't find any good examples so after spending some time getting it working I thought I'd post a simple version of the code I used in case it helps anyone else:
class MyTest {
#Autowired
private SomeService service;
#Autowired
private ConfigurableApplicationContext applicationContext;
public void init(Class<?> testClass) throws Exception {
TestContextManager testContextManager = new TestContextManager(testClass);
testContextManager.prepareTestInstance(this);
}
#After
public void tearDown() throws Exception {
applicationContext.close();
}
#Test
public void test1() throws Exception {
init(ConfigATest.class);
service.doSomething();
// assert something
}
#Test
public void test2() throws Exception {
init(ConfigBTest.class);
service.doSomething();
// assert something
}
#ContextConfiguration(classes = {
ConfigATest.ConfigA.class
})
static class ConfigATest {
static class ConfigA {
#Bean
public SomeService someService() {
return new SomeService(new A());
}
}
}
#ContextConfiguration(classes = {
ConfigBTest.ConfigB.class
})
static class ConfigBTest {
static class ConfigB {
#Bean
public SomeService someService() {
return new SomeService(new B());
}
}
}
}
I use these approaches when I'm have to solve this:
Manually build the context in a setup method instead of using annotations.
Move the common test code to a base class and extend it. That allows me to run the tests with different spring contexts.
A mix of the two above. The base class then contains methods to build spring contexts from fragments (which the extensions can override). That also allows me to override test cases which don't make sense or do extra pre/post work in some tests.
Keep in mind that annotations only solve generic cases. You'll have to replicate some or all of their work when you leave the common ground.
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.
There have been many questions regarding request scoped management in unit tests and mainly answer is to do not test the scope management, as its a Spring Framework task and it should take care that it works properly. So advice, for example, would be to replace the request scope with thread or prototype type scope in the XML configuration file.
For most of the tests its enough, there are no complaints about not registered "request" scope and tests are running fine. But I do have one case where it is not enough.
Consider following case:
#Component
#Scope("request")
public class MyService {
#Autowired
private MyComponent component;
public void doSomething(String param) {
component.doTheThing(param);
}
}
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({"classpath:my-scope-tweaks.xml"})
public class MyServiceTest {
#Autowired
private MyService service;
#Autowired
private MyComponent component;
#Test
public void test1() {
service.doSomething("aaa");
assertEquals("AAA", component.getTheThing());
}
#Test
public void test1() {
service.doSomething("bbb");
assertEquals("BBB", component.getTheThing());
}
}
I want to test MyService, which is request-scoped. MyComponent is request scope as well.
Variant A
If I replace the request scope with SimpleThreadScope, then in by both tests I would receive the same instance of MyService and MyComponent, so for example test2() could receive bad results from MyComponent as it could internally contain some internal "trash" from previous test1()
Variant B
If I replace request scope with prototype scope - I would get the case where my test methods are receiving different instances of MyComponent as MyService does - so I cannot perform any assertions on them.
So what I would need is kind of test method-related request scope, where all request-scoped beans remain just during the test1() method and then gets destroyed, so within next test2() they would be created newly again.
Is it possible?
This may not be what you're looking for, but why are you using Spring to manage your test classes in the first place? That seems like overkill to me. For unit testing, you shouldn't need a DI container. Just mock the dependencies and focus on the encapsulated functionality of the Component or Service you're testing.
You won't be able to do that, though, while using field injection. You'll need to convert to constructor or method injection.
It is possible to receive a new context for every method in your unit test by annotating the method with #DirtiesContext. This allows you to manipulate beans in the application context for one unit test and not have it affect the others.
Documentation
Your example annotated with #DirtiesContext:
#Component
#Scope("request")
public class MyService {
#Autowired
private MyComponent component;
public void doSomething(String param) {
component.doTheThing(param);
}
}
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({"classpath:my-scope-tweaks.xml"})
public class MyServiceTest {
#Autowired
private MyService service;
#Autowired
private MyComponent component;
#Test
#DirtiesContext
public void test1() {
service.doSomething("aaa");
assertEquals("AAA", component.getTheThing());
}
#Test
#DirtiesContext
public void test2() {
service.doSomething("bbb");
assertEquals("BBB", component.getTheThing());
}
}
If all of your test methods within your class require a new context you can also just annotate the class as follows:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({"classpath:my-scope-tweaks.xml"})
#DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
public class MyServiceTest {
//Tests Here...
}