Is creating the same beans for tests in #Configuation violating DRY? - java

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;
}

Related

Why does autowired repository result in a NullPointerException in Junit4 context?

I'm new to Java development so sorry in advance if I'm not using the appropriate terms.
Whenever I run a test on a class that needs to save something in my database, I face a NullPointerException on the Autowired repository.
I use Junit4, here are code snippets :
application-test.properties
spring.datasource.url=jdbc:tc:mysql:8.0.29://localhost:3306/MYSERVICE
MyService.java
#Component
class MyService {
#Autowired MyRepository myRepository;
public void mainFunction() {
myRepository.saveSomething();
}
}
MyRepository.java
#Repository
public interface MyRepository extends JpaRepository<T, Long> {
void saveSomething();
}
MyServiceTest.java
public class myServiceTest extends TestConfiguration {
#Rule
public MySQLContainer mysql = new MySQLContainer();
#InjectMocks MyService myService;
#Test
public void mainFunctionTest() {
myService.mainFunction()
}
}
MyServiceTestApplication.java
#SpringBootApplication(exclude = {
DataSourceAutoConfiguration.class
})
public class MyServiceTestApplication{
}
TestConfiguration.java
#RunWith(SpringRunner.class)
#SpringBootTest(classes = MyServiceTestApplication.class)
#ActiveProfiles("test")
public abstract class TestConfiguration {
}
When I run the test in debug mode, I can see that myRepository is null
Any help would be highly appreciated
Thanks :)
Edit 01/08/2022 : Add #Component on MyService
Edit 01/08/2022 (2) : Add MyServiceTestApplication.java and TestConfiguration.java
It seems, you forgot to annotate the class MyService with #Service.
With this annotation being made at that class, the Spring framework will recognize it:
This annotation serves as a specialization of #Component, allowing for
implementation classes to be autodetected through classpath scanning.
Given that the rest of the configuration is working, the #Autowired dependency injection mechanism will hereby provide you with an instance of the #Repository you requested, at runtime, here your test setup.

How to use #SpringBootTest class mockbean in test class?

#SpringBootTest(classes = TestConfig.class)
class ServiceIntegTest {
}
class TestConfig {
#MockBean
RandomExecutor randomExecutor
}
I want to use RandomExecutor mock bean in ServiceIntegTest class, how to do it ?
I am not mocking methods of the bean in TestConfig class itself, because in ServiceIntegTest class there are various tests in which methods of RandomExecutor have to behave in different ways.
You do not have to #MockBean in your config, you have to do it in the test class. Then you can mock it in some test classes and use a real instance in others.
Have a look at a basic usage of #MockBean:
https://www.infoworld.com/article/3543268/junit-5-tutorial-part-2-unit-testing-spring-mvc-with-junit-5.html
You use a MockBean as you would a #Mock It just get injected into a spring context you're using for your test.
#SpringBootTest
class ServiceIntegTest {
#MockBean RandomExecutor randomExecutor;
// this service gets autowired from your actual implementation,
// but injected with the mock bean you declared above
#Autowired
YourService underTest;
#Test
void verifyValueUsed() {
final int mockedValue = 5;
when(randomExecutor.getThreadCount()).thenReturn(mockedValue);
int result = underTest.getExecutorThreads();
assertThat(result).isEqualTo(mockedValue);
}
#Test
void verifyExecutorCalled() {
underTest.performAction("argument");
verify(randomExecutor).executorMethod("argument");
}
}

How to add a bean in SpringBootTest

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.

Java Spring: Mocking Context in Multiple Files

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.

How to mock behavior for Springockito mocks?

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();
}
}

Categories

Resources