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.
Related
My goal is to use an in-memory database for these unit tests, and those dependancies are listed as:
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
runtimeOnly("com.h2database:h2")
So that the repository instance actually interacts with a DB, and I dont just mock return values.
The problem is that when I run my unit test, the repository instance inside the service instance is null.
Why is that? Am I missing some annotation on the unit test class to initialise the repository instance?
This is the console output when running my unit test:
null
java.lang.NullPointerException
at com.my.MyService.findAll(MyService.java:20)
at com.my.MyTest.testMy(MyTest.java:23)
My unit test class:
public class MyTest {
#MockBean
MyRepository myRepository;
#Test
void testMy() {
MyService myService = new MyService();
int size = myService.findAll().size();
Assertions.assertEquals(0, size);
}
}
My service class:
#Service
public class MyService {
#Autowired
MyRepository myRepository;
public List<MyEntity> findAll() {
System.out.println(myRepository); // null
return (List<MyEntity>) myRepository.findAll(); // throws NullPointerException
}
#Transactional
public MyEntity create(MyEntity myEntity) {
myRepository.save(myEntity);
return myEntity;
}
}
My repository class:
#Repository
public interface MyRepository extends CrudRepository<MyEntity, Long> {
}
My entity class:
#Entity
public class MyEntity {
#Id
#GeneratedValue
public Long id;
}
Why is that? Am I missing some annotation on the unit test class to initialise the repository instance?
Basically yes :)
You need to initialise a Spring Context by Annotating your Testclass with #SpringBootTest
The other Problem you have is that you create your MyService Object manually.
By doing so SpringBoot has no chance to inject any Bean for you. You can fix this by simply injecting your MyService in your Testclass. Your Code should look something like this:
#SpringBootTest
public class MyTest {
#Autowired
private MyService myService;
#Test
void testMy() {
int size = myService.findAll().size();
assertEquals(0, size);
}
}
To use #MockBean annotation, you have to use SpringRunner to run the test. Use #RunWith Annotation on top of your test class and pass SpringRunner.class.
#RunWith(SpringRunner.class)
public class MyTest {
#MockBean
MyRepository myRepository;
#Test
void testMy() {
MyService myService = new MyService();
int size = myService.findAll().size();
Assertions.assertEquals(0, size);
}
}
The problem here is your service implementation. Using #Autowired to inject the dependency will work when you run the whole app, but it do not allow you to inject a custom dependency when you'll need it, and a good example of this is testing.
Change your service implementation to:
#Service
public class MyService {
private MyRepository myRepository;
public MyService(MyRepository myRepository){
this.myRepository = myRepository;
}
public List<MyEntity> findAll() {
System.out.println(myRepository); // null
return (List<MyEntity>) myRepository.findAll(); // throws NullPointerException
}
#Transactional
public MyEntity create(MyEntity myEntity) {
myRepository.save(myEntity);
return myEntity;
}
}
This constructor will be called by spring. Then change your test to:
public class MyTest {
#Mock
MyRepository myRepository;
#Test
void testMy() {
MyService myService = new MyService(myRepository);
int size = myService.findAll().size();
Assertions.assertEquals(0, size);
}
}
Note I have replaced #MockBean to #Mock as the previous annotation is for injecting a mock bean into the spring context, which is not needed if you're doing unit testing. If you want to boot spring context (which I would not recommend you) you need to configure your test class with #SpringBootTest or some of the other available alternatives. That will convert your test into an integration test.
PD: This test will not work if you don't provide a mock to myRepository.findAll(). Mockito default behaviour is to return null, but you're expecting it to return 0, so you'll need to do something like given(myRepository.findAll()).willReturn(0).
I believe you wish to write an integration test. Here you could remove the MockBean annotation and simply autowire your repository. Also, run with The SpringRunner class.
#RunWith(SpringRunner.class)
public class MyTest {
#Autowired
MyRepository myRepository;
#Autowired
MyService myService
#Test
void testMy() {
int size = myService.findAll().size();
Assertions.assertEquals(0, size);
}
}
This should work
I have a Repository MyRepository which is a #Repository. This repository is used by one of my rest controllers. What I want to test is if authorization of my rest controller works properly, thus my tests use #WithUserDetails. I want to mock a call to MyRepository by following this tutorial. When I run my tests I get an exception saying:
org.mockito.exceptions.misusing.MissingMethodInvocationException:
when() requires an argument which has to be 'a method call on a mock'.
For example:
when(mock.getArticles()).thenReturn(articles);
Also, this error might show up because:
1. you stub either of: final/private/equals()/hashCode() methods.
Those methods *cannot* be stubbed/verified.
Mocking methods declared on non-public parent classes is not supported.
2. inside when() you don't call method on mock but on some other object.
Through some debugging I found out that my MockConfig#myRepository method is not being called.
src/main/java/com.example
MyRepository
#Repository
interface MyRepository extends CrudRepository<MyEntity, Long> {}
src/test/java/com.example
MockConfig
#Profile("test")
#Configuration
public class MockConfig
{
#Bean
#Primary
public MyRepository myRepository()
{
return Mockito.mock(MyRepository.class);
}
}
MyTestClass
#ActiveProfiles("test")
#AutoConfigureMockMvc
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class)
#TestExecutionListeners({
DependencyInjectionTestExecutionListener.class
})
class MyTestClass
{
#Autowired
private MockMvc mvc;
#Autowired
private MyRepository myRepository;
#Test
#WithUserDetails("some_user")
public void testWithCorrectPermissions()
{
long entityId = 1;
MyEntity mockReturnValue = new MyEntity();
Mockito.when(myRepository.findOne(entityId)).thenReturn(mockReturnValue);
Mockito.when(myRepository.save(mockReturnValue)).thenReturn(mockReturnValue);
this.mockMvc.perform(post("my/api/path")).andExpect(status().isOk());
}
}
Try with the solution proposed in How to exclude a #Repository from component scan when using Spring Data Rest
Add the following annotation to your test class
#EnableJpaRepositories(excludeFilters = {#ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {MyRepository.class})})
class MyTestClass
(...)
If you want to mock the dependency(eg repository) for your testing class, I would suggest you to use MockitoJUnitRunner.class as SpringRunner.class will initialise the Spring application content, which will cause the testing to be slower and also more dependencies required depending on your project configuration.
So, for your MyTestClass
#RunWith(MockitoJUnitRunner.class)
public class MyTestClass{
#Mock
private MyRepository myRepository;
private MyTest myTest;
#Before
public void setUp() throws Exception {
myTest = new MyTest(myRepository);
}
#Test
public void test(){
...
when(myRepository.get(anyInt()).thenReturn(new MyEntity());
...
}
There is some reference here.
If you insist to test using the current implementation, it might be that the MyRepository was scanned by the Spring Data and the bean was initialised by it. You might want to disable the component scanning as recommended by user2456718.
I want to know how to write Junit test for Spring Repository classes.
As example :
class-AccountMoveActionDet
Jpa interface-AccountMoveActionDetlJpaRepository
And I want to test this repository class work or not.Spring Jpa support some methods like
List findAll();
deleteAll();
I wrote a class just like below:
#RunWith(SpringJUnit4ClassRunner.class)
public class AccountTypeMovementJpaRepositoryTest extends AbstractJpaTest {
#Autowired
AccountTypeMovementJpaRepository accountTypeMovementJpaRepository;
#Override
public void test() {
executeSqlScript("/fixtures/accountTypeMovementJpa.sql");
assertEquals("accountTypeMovementJpaRepository Test", accountTypeMovementJpaRepository.findAll().size(),
JdbcTestUtils.countRowsInTable(getJdbcTemplate(), "COF5REP"));
}
}
Error creating bean with name
'com.gayan.cmp.jparepositories.test.AccountTypeMovementJpaRepositoryTest':
Please help me to resolve this.
If you use spring-boot 1.4 and above the best place to start, Testing the JPA slice :
#RunWith(SpringRunner.class)
#DataJpaTest
public class UserRepositoryTests {
#Autowired
private TestEntityManager entityManager;
#Autowired
private UserRepository repository;
#Test
public void findByUsernameShouldReturnUser() {
this.entityManager.persist(new User("sboot", "123"));
User user = this.repository.findByUsername("sboot");
assertThat(user.getUsername()).isEqualTo("sboot");
assertThat(user.getVin()).isEqualTo("123");
}
}
I was wondering if there is a way to reduce the amount of boilerplate that we are currently writing for out integration tests.
The main culprit is ContextConfiguration that we send 7 distinct strings into currently.
One of our tests looks like this (payload code removed):
#ContextConfiguration(locations = {"classpath:properties-config.xml",
"classpath:dataSources-config.xml",
"classpath:dao-config.xml",
"classpath:services-config.xml",
"classpath:ehcache-config.xml",
"classpath:test-config.xml",
"classpath:quartz-services.xml"})
#RunWith(SpringJUnit4ClassRunner.class)
#Category(IntegrationTest.class)
public class TerminalBuntsPDFTest {
#Autowired
private JobService jobService;
#Test
public void testCode() throws SystemException {
assertTrue("Success", true);
}
}
And the specification of what xml files to load takes up a lot of space. We are in a (very slow) process of migrating away from xml towards annotations, but there is a lot of work left to do in that project.
We are using spring 3.2.
The annotation based approach is to create a Spring Configuration Java class like this:
#Configuration("testConfig")
#ImportResource({
"dataSources-config.xml",
"dao-config.xml",
"services-config.xml"
})
public class TestConfiguration {
// TO create a spring managed bean
#Bean
MyBean myBean() {
return new MyBean();
}
}
Then you can annotate your test class like so to load the configuration:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(
classes=TestConfiguration.class,
loader=AnnotationConfigContextLoader.class
)
#Category(IntegrationTest.class)
public class TerminalBuntsPDFTest {
This is just a light example that probably won't compile but should get you on the right track
Some relevant docs:
http://www.tutorialspoint.com/spring/spring_java_based_configuration.htm
http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Configuration.html
What about such pattern:
#ContextConfiguration(locations = {"classpath:properties-config.xml",
"classpath:dataSources-config.xml",
"classpath:dao-config.xml",
"classpath:services-config.xml",
"classpath:ehcache-config.xml",
"classpath:test-config.xml",
"classpath:quartz-services.xml"})
#RunWith(SpringJUnit4ClassRunner.class)
#Category(IntegrationTest.class)
public abstract class BaseTest {
}
// ....
public class TerminalBuntsPDFTest extends BaseTest {
#Autowired
private JobService jobService;
#Test
public void testCode() throws SystemException {
assertTrue("Success", true);
}
}
// ....
public class TerminalBuntsPDFTest2 extends BaseTest {}
This will allow you to place configuration only once in parent abstract class.
I have any issue in my unit test where I have something along the lines of this. The mock injection get overridden on the someService if the blargh function is annotated with Transactional. If I remove the Transactional the mock stays there. From watching the code it appears that Spring lazily loads the services when a function in the service is annotated with transactinal, but eagerly loads the services when it isn't. This overrides the mock I injected.
Is there a better way to do this?
#Component
public class SomeTests
{
#Autowired
private SomeService someService;
#Test
#Transactional
public void test(){
FooBar fooBarMock = mock(FooBar.class);
ReflectionTestUtils.setField(someService, "fooBar", fooBarMock);
}
}
#Service
public class someService
{
#Autowired FooBar foobar;
#Transactional // <-- this causes the mocked item to be overridden
public void blargh()
{
fooBar.doStuff();
}
}
Probably you could try to implement your test in the following way:
#Component
#RunWith(MockitoJUnitRunner.class)
public class SomeTests
{
#Mock private FooBar foobar;
#InjectMocks private final SomeService someService = new SomeService();
#Test
#Transactional
public void test(){
when(fooBar.doStuff()).then....;
someService.blargh() .....
}
}
I could not try it right now as don't have your config and related code. But this is one of the common way to test the service logic.
Use the Spring #Profile functionality - beans can be associated to a certain group, and the group can be activated or deactivated via annotations.
Check this blog post and the documentation for more detailed instructions, this is an example of how to define production services and two groups of mock services:
#Configuration
#Profile("production")
public static class ProductionConfig {
#Bean
public InvoiceService realInvoiceService() {
...
}
...
}
#Configuration
#Profile("testServices")
public static class TestConfiguration {
#Bean
public InvoiceService mockedInvoiceService() {
...
}
...
}
#Configuration
#Profile("otherTestServices")
public static class OtherTestConfiguration {
#Bean
public InvoiceService otherMockedInvoiceService() {
...
}
...
}
And this is how to use them in the tests:
#ActiveProfiles("testServices")
public class MyTest extends SpringContextTestCase {
#Autowired
private MyService mockedService;
// ...
}
#ActiveProfiles("otherTestServices")
public class MyOtherTest extends SpringContextTestCase {
#Autowired
private MyService myOtherMockedService;
// ...
}
I have the exact same problem and I solve it by using Mockito.any() for the arguments
eg:
when(transactionalService.validateProduct(id)).thenReturn("")
=> when(transactionalService.validateProduct(Mockito.any())).thenReturn("")