Trying to make mocked Spring Context for unit tests purpose. Our Spring Configuration is pretty big and as for now trying to get this thing using Spring + Springockito Annotations.
Problem that I've stumbled upon is that I'd like to have multiple Java Classes taking care of the Context Creation/Mocking. As for now this looks like this (let's say Class a takes class B and List of ClassCs as constructor arguments):
//declaring context classes below
#ContextConfiguration(loader = SpringockitoAnnotatedContextLoader.class,
classes = {
ClassA.class,
ClassB.class,
SubClassCOne.class,
SubClassCTwo.class,
... //list goes on and on with more mocks
}
)
public class Configurator {
#Autowired
ClassA classA;
#ReplaceWithMock
ClassB classB;
#Autowired
List<ClassC> classesC;
#Autowired
SubClassCOne subclassCOne;
....
Problem is that List of C subclasses is far bigger than I'd like to have here (not to mention I'm putting whole responsibility on just one class), so I thought of another Class that will take care of creations like this:
ListProvider.java:
//THIS ANNOTATION WON'T BE TAKEN INTO ACCOUNT
#ContextConfiguration(loader = SpringockitoAnnotatedContextLoader.class,
classes = {
SubClassCOne.class,
SubClassCTwo.class,
...
}
)
public class ListProvider {
#Autowired
List<ClassC> classesC;
...
public List<ClassC> getClassesC(){
return classesC;
}
...
Configurator.java:
#ContextConfiguration(loader = SpringockitoAnnotatedContextLoader.class,
classes = {
ClassA.class,
ClassB.class,
ListProvider.class,
...
}
)
public class Configurator {
#Autowired
ClassA classA;
#ReplaceWithMock
ClassB classB;
#Autowired
ListProvider listProvider
#Autowired //probably not autowired anymore?
List<ClassC> classesC;
...
//then somewhere it'll take classesC from listProvider using getter?
But then the problem comes that even though ListProvider will be autowired correctly, it won't make use of #ContextConfiguration classes and it won't find any ClassC subclasses unless I won't provide everything in Configurator.java, which is exactly what I'd like to avoid.
Is there a change to split this Context Configuration up into multiple files?
Separate out your config into default and test using profiles:
#Configuration
#ComponentScan(basePackages = "com.greg")
public class MainConfig {
}
#Configuration
#Profile("test")
public class TestConfig {
}
Then run your test with a test profile
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes=MainConfig .class)
#ActiveProfiles("test")
public class ATest {
So the test will pick up MainConfig at all time but will only pick up TestConfig when the profile is set to test. The #Profile can also be used to distinguish which beans are loaded on a scan.
Related
I want to test class A that has a few dependencies from beans B and C. I wonder if copying a code of these beans from program code to test configuration violated dry?
P.S. I am completely new to testing and I do know how dumb the question is.
You don't need to copy code across from /main to /test. Spring provides a variety of ways to inject beans into tests.
First, you can use #SpringBootTest to load the Spring Context for your test and then instantiate your class under test using #Autowired
#SpringBootTest(classes = {ClassA.class, ClassB.class, ClassC.class})
public class ClassATest {
#Autowired
ClassA classA;
}
Alternatively you can mock your dependencies using Mockito (which comes as part of the Spring-Boot-Starter-Test package)
#ExtendWith(MockitoExtension.class)
public class ClassATest {
#Mock
ClassB mockB;
#Mock
ClassC mockC;
#InjectMocks
Class A classA;
}
As you mentioned you can also use #TestConfiguration to create a configuration for your test and inject those beans into your class using #Import and #Autowired
#TestConfiguration
public class ClassATestConfig {
#Bean
public ClassA classA() {
return new ClassA(classB(), classC());
}
#Bean
public ClassB classB() {
return new ClassB();
}
#Bean
public ClassC classC() {
return new ClassC();
}
}
Then inject the configured bean as so.
#ExtendWith(SpringExtension.class)
#Import(ClassATestConfig.class)
public class ClassATest {
#Autowired
ClassA classA;
}
I am learning Spring while I like the idea of using #Component and #Autowired to let Spring manage the dependent bean. For example, I have a Service class and Builder Class I can do with
// SomeService.java
#Service
public class SomeService {
// SomeBuilder is a #Component class
#Autowired
SomeBuilder someBuilder;
}
// SomeController.java
#Component
public class SomeController {
#Autowired
SomeService someSerivce;
}
Spring would take care of the creation of from SomeController to SomeService to SomeBuilder with the usage of #Autowired. However, now my SomeService class needs a private field which is NOT a Component class, just a plain context object, for example
// SomeService.java
#Service
public class SomeService {
#Autowired
SomeBuilder someBuilder;
private SomeContext someContext;
// Plan A: Using constructor to initiate the private field. However, I cannot use #Autowired to initiate SomeService in SomeController anymore as it requires a parameterless constructor
// Plan B: using #Autowired on constructor level, I cannot use this because SomeContext is not a #Component class
//public SomeService(SomeContext someContext) {
//this.someContext = someContext;
//}
// Plan C: This seems work but I kinda feel it's not the right way, as usually private field are initiated through constructor
//public void init(SomeContext someContext) {
// this.someContext = someContext;
//}
// demo usage of someContext
public someAnswer realMethod() {
System.out.println(someContext.getName());
}
}
Now I have no idea how to inject the someContext now, I used
plan A: Assign the private field using class constructor
plan B: Using #Autowired on constructor level
plan C: Using a wired method to assign the private field.
but I am not satisfied and don't have a clear way of doing the right approach.
First lets take a look at your plans and bust some myths/misunderstandings.
Plan A: Using constructor to initiate the private field. However, I cannot use #Autowired to initiate SomeService in SomeController anymore as it requires a parameterless constructor
Great plan, and the way to go. #Autowired doesn't depend on having a default constructor. It only indicates that you want the field to be injected with an object of that type. How that object comes to live (default constructor, constructor with arguments) doesn't matter for #Autowired. So that part of your understanding is just wrong.
using #Autowired on constructor level, I cannot use this because SomeContext is not a #Component class
If there is just a single constructor in a bean Spring will automatically use that to satisfy the dependencies. So in this case you don't need #Autowired. A bean doesn't have to be an #Component, a bean is just an instance of a class available to the application context. One way of achieving that is by marking it as an #Component but there are other ways as well. Like defining an #Bean method in in an #Configuration class to construct the bean.
#Configuration
#ComponentScan("your.base.package")
public class YourConfiguration {
#Bean
public SomeContext someContext() {
return new SomeContext();
}
}
Something along those lines. It will detect the #Component annotated classes through the #ComponentScan and will create a bean of type SomeContext for use as a bean.
Plan C: This seems work but I kinda feel it's not the right way, as usually private field are initiated through constructor
All your fields should be private not just the ones initialized in a constructor, so also the #Autowired ones. You don't want those fields to be, easily, accessible from the outside so they can be modified. So make them private.
That all being said, go with constructor injection over field injection or setters/methods for injection. It is clearer and less hidden than field injection and the way to go for mandatory dependencies (for optional dependencies you can use a setter/method).
So using the above config and below classes, it should "just work (tm)".
// SomeService.java
#Service
public class SomeService {
// SomeBuilder is a #Component class
private final SomeBuilder someBuilder;
private final SomeContext someContext;
public SomeService(SomeBuilder someBuilder, SomeContext someContext) {
this.someBuilder=someBuilder;
this.someContext=someContext;
}
}
// SomeController.java
#Component
public class SomeController {
private final SomeService someSerivce;
public SomeController(SomeService someService) {
this.someService=someService;
}
}
The question seems extremely simple, but strangely enough I didn't find a solution.
My question is about adding/declaring a bean in a SpringBootTest, not overriding one, nor mocking one using mockito.
Here is what I got when trying the simplest implementation of my real need (but it doesn't work):
Some service, bean, and config:
#Value // lombok
public class MyService {
private String name;
}
#Value // lombok
public class MyClass {
private MyService monitoring;
}
#Configuration
public class SomeSpringConfig {
#Bean
public MyClass makeMyClass(MyService monitoring){
return new MyClass(monitoring);
}
}
The test:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = { SomeSpringConfig.class })
public class SomeSpringConfigTest {
private String testValue = "testServiceName";
// this bean is not used
#Bean
public MyService monitoringService(){ return new MyService(testValue); }
// thus this bean cannot be constructed using SomeSpringConfig
#Autowired
public MyClass myClass;
#Test
public void theTest(){
assert(myClass.getMonitoring().getName() == testValue);
}
}
Now, if I replace the #Bean public MyService monitoring(){ ... } by #MockBean public MyService monitoring;, it works. I find it strange that I can easily mock a bean, but not simply provide it.
=> So how should I add a bean of my own for one test?
Edit:
I think ThreeDots's answer (create a config test class) is the general recommendation.
However, Danylo's answer (use #ContextConfiguration) fit better to what I asked, i.e. add #Bean directly in the test class.
Spring Test needs to know what configuration you are using (and hence where to scan for beans that it loads). To achieve what you want you have more options, the most basic ones are these two:
Create configuration class outside the test class that includes your bean
#Configuration
public class TestConfig {
#Bean
public MyService monitoringService() {
return new MyService();
}
}
and then add it to to test as configuration class #SpringBootTest(classes = { SomeSpringConfig.class, TestConfig.class })
or
If you only need to use this configuration in this particular test, you can define it in static inner class
public class SomeSpringConfigTest {
#Configuration
static class ContextConfiguration {
#Bean
public MyService monitoringService() {
return new MyService();
}
}
}
this will be automatically recognized and loaded by spring boot test
Simply add the config as
#ContextHierarchy({
#ContextConfiguration(classes = SomeSpringConfig.class)
})
What i am using in this cases is #Import:
#DataJpaTest(showSql = false)
//tests against the real data source defined in properties
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
#Import(value = {PersistenceConfig.class, CustomDateTimeProvider.class})
class MessageRepositoryTest extends PostgresBaseTest {
....
Here i am using a pre configured "test slice".
In this case a need to add my JpaAuditingConfig.
But why not just adding the other beans as you did with your SomeSpringConfig.class ?:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = { SomeSpringConfig.class, OtherBean.class })
public class SomeSpringConfigTest {
...
Everything listed in test will be injectable directly, all not declared must be added as mocks.
I have to create a instance of a class, that have autowired elements, for test.
public class MyClass extends SomeOtherClass {
#Autowired
public MyClass(OtherClass1 one, OtherClass2 two){
super(one, two)
}
}
How can i in code create instance of this class, with the arguments wired in though spring?
Your test can be made Spring-aware if you use the SpringJUnit4ClassRunner to read in your Spring Context to be used in the test. For instance:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations={"the-config.xml"})
public final class MyClassTests {
#Autowired
private MyClass testee;
#Test
public void testSomething() {
assertThat(testee).doesSomethingExpected();
}
}
Note that you should reuse as much of your production config as possible and not create a parallel Spring Context config that mirrors it.
Instead of passing the other elements in as constructor arguments, you Autowire them as properties. Spring will then inject the objects.
public class MyClass extends SomeOtherClass {
#Autowired
private OtherClass1 one;
#Autowired
private OtherClass2 two
public MyClass(){
super(one, two)
}
}
Edit: Based on http://www.mkyong.com/spring/spring-auto-wiring-beans-with-autowired-annotation/, adding #Autowired to the constructor is also valid.
If you want to Autowire MyClass, you must annotate it with #Component or a similar annotation such as #Service.
#Component
public class MyClass extends SomeOtherClass
Then, you can use it in other classes
public class ClassThatUsesMyClass {
#Autowire
private MyClass myClass;
}
If I create a mock in my spring context file using Springockito as described here, how do I mock some behavior for it?
What I'm trying to do:
ClassA is being tested.
ClassB is autowired in ClassA.
ClassB is being mocked with Springockito.
ClassA needs ClassB to do something in its PostConstruct.
I need to mock ClassB to do that something, since it can't and shouldn't really do it.
Doing this is straight forward without using Springockito (using Mockito straight up), but I need to autowire these beans and use Spring in my tests. Any help is appreciated.
Note that new springockito-annotations help to achieve the same goal without messing with xml context and extra helper classes:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(loader = SpringockitoContextLoader.class, locations = "classpath:test-config.xml")
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
public class MemoAutoTest extends AbstractJUnit4SpringContextTests {
#Autowired
private ClassA classA;
#Autowired #ReplaceWithMock
private ClassB classB;
#Test
public void testClassA() {
// stub B
when(classB.foo()).thenReturn(true);
when(classB.bar()).thenReturn(42);
// test A
}
}
This would cause ClassB to be replaced with mock at main application context initialization.
I'm not familiar with Springockito, but it looks interesting for some narrow cases - namely integration testing with mocking just a bit.
Anyway, it looks like for a straightforward use case you extend AbstractJUnit4SpringContextTests, you could also autowire ClassB in your test just like you do in ClassA. Then you could define your expected behavior for ClassB in your setup method.
But I think that you need to set up some behavior for the ClassB bean before you get access to it in your setup method. In that case, you may need another bean to set up ClassB to do the expected behavior. So your testContext.xml would have something like this in it:
<bean id="classA" class="com.jarvis.ClassA" depends-on="classBMockSetter" />
<mockito:mock id="classB" class="com.jarvis.ClassB" />
<bean id="classBMockSetter" class="com.jarvis.test.ClassBMockSetter">
<property name="classB" ref="classB" />
</bean>
The ClassBMockSetter would look something like:
public class ClassBMockSetter {
private ClassB classB;
public void setClassB(ClassB classB) {
this.classB = classB;
given(classB.foo()).willReturn(true);
given(classB.bar()).willReturn(42);
}
}
I think that would work, but at that point, isn't it easier to just hand-code your mock ClassB?
What is worked for me is using #InjectMocks notation.
(See https://bitbucket.org/kubek2k/springockito/wiki/Home)
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(loader = SpringockitoContextLoader.class, locations = "classpath:test-config.xml")
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
public class MemoAutoTest extends AbstractJUnit4SpringContextTests {
#Autowired
private ClassA classA;
#Autowired #InjectMocks
private ClassB classB;
#Test
public void testClassA() {
// stub B
when(classB.foo()).thenReturn(true);
when(classB.bar()).thenReturn(42);
// test A
classA.doSomethingThatInternallyCallClassBFoo();
}
}