Let's assume I have a MyServiceTest class which is extended by some HelperTest class which has context configuration defined and injected service like this:
#ContextConfiguration(locations = { "classpath:META-INF/context.xml" })
public abstract class HelperTest {
#Autowired
private MyService myService;
}
This helper class helps test classes to prepare the system for testing.
Now I want to test MyService class which means that I need to inject this service in my MyServiceTest class:
#RunWith(SpringJUnit4ClassRunner.class)
public class MyServiceTest extends HelperTest {
#Autowired
private MyService myService;
}
But in general because MyServiceTest is inherited I could just define MyService in HelperTest as protected and use it in MyService class.
Is it right thing to do or there are some other things which might influence when I use bean injection via spring in this way?
Related
I am trying to organize our usage of #MockBean to reduce the number of ApplicationContexts in use during our testing. My approach is to put shared
mocks in super classes and then inherit into the actual test code.
I have a hierarchy like this:
BaseTest:
#ActiveProfiles({"test", "human-readable-logging"})
#ContextConfiguration(classes = Main.class)
#ExtendWith(SpringExtension.class)
#SpringBootTest(properties = {"spring.cloud.gcp.credentials.location="})
#AutoConfigureMockMvc(print = MockMvcPrint.NONE)
public class BaseTest {}
BaseUnitTestMocks: this has two subclasses
public class BaseUnitTestMocks extends BaseTest {
#MockBean private ServiceA serviceA;
#MockBean private ServiceB serviceB;
... accessors ...
BaseUnitTest: this is on the inheritance line with the problem.
#Tag("unit")
#ActiveProfiles("unit-test")
public class BaseUnitTest extends BaseUnitTestMocks {}
BaseUnitTestPlusOneMock: this mock seems to not make it into the application context
public class BaseUnitTestPlusOneMock extends BaseUnitTest {
#MockBean private ServiceC serviceC;
... accessor ...
}
The test class looks like this:
public class MyTest extends BaseUnitTestPlusOneMock {
private static final FAKE_THING = "fake_thing";
#BeforeEach
public void setup() throws IOException {
when(mockServiceC().getRequiredThing(any())).thenReturn(FAKE_THING);
}
... tests ...
Expected Result:
The mock is included in the application context and used in the Autowired in ServiceC.
Actual Result:
This works when run from the debugger in IntelliJ. When run in a clean environment (cleaned project and stopped all gradle daemons), not in the debugger it fails because the actual ServiceC is used.
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.
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 found the following code.
public class Foo {
#Autowired
private MyService myService = new MyService();
}
Does it mean that Spring would overwrite the instance of myService which is created when Foo is created?
This code makes it possible to use Foo in Junit-context without starting in a springcontext.
Is it okay to do things like that?
No, it's not okay to do things like that. Instead:
#Service
public class Foo {
private MyService myService;
#Autowired
public Foo(MyService myService){
this.myService = myService;
}
}ยด
This way Spring injects your required dependency when constructing the Service. For a JUnit context you just need to provide it yourself, it could be as a real instance or as a mock:
#Test
public void testFoo(){
MyService mockService = mock(MyService.class);
Foo foo = new Foo(mockService);
foo.myMethod();
verify(/*Check that foo has done whatever you want with MyService*/);
}
Adding to above answer, you can also run it using ContextConfig as below if you are using Spring 4
Using SpringBoot, you can create bean using #Bean annotation and then autowire it
class MyTestConfig{
#Bean
public MyService myService(){
return new MyService();
};
}
Run test case with MyTestConfig as contextConfiguration and autowire as
#Autowired
public MyService myService
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;
}