I'm learning unit testing with Spring Boot. I've created a stub object from Spring Data repository class using Mockito. All works fine, here's a code:
#SpringBootTest
class UserServiceTest {
#MockBean
private UserRepository userRepository;
#BeforeAll
public void configureMock() {
User user = new User("mishenev",
"Dmitrii",
"Mishenev",
"123",
"mishenev.8#gmailcom",
new UserSettings());
when(userRepository.findById(anyLong())).thenReturn(Optional.of(user));
when(userRepository.findUserByUserName(anyString())).thenReturn(user);
// .. Tests
}
But it's kind of boilerplate style to use this #BeforeAll test repository configuration for each class, if we don't need different stub behaviour. Then i tried to use #TestConfiguration
#TestConfiguration
public class SpringRepositoryTestConfiguration {
#Bean
public UserRepository userRepository () {
UserRepository userRepository = Mockito.mock(UserRepository.class);
// Configuring mock's behaviour
return userRepository;
}
}
After that i used
#SpringBootTest(classes = SpringRepositoryTestConfiguration.class)
But i can't using #MockBean to autowire repository in UserService now.
I want to understand is it possible to take out and use all test Repository type stubs in one configuration class. Thank you in advance!
Just use this
class TestConfig {
#Bean
UserRepository userRepository() {
final UserRepository repo = mock(UserRepository.class);
.. do mocking ..
return repo;
}
}
Then you can just #Import(TestConfig.class) where you need it
Related
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'm trying to test a Spring Boot service that depends on both a repository and another service. I'm using TestContainers to verify that the service interacts correctly with the repository. However, I need to mock the other service in order to provide the test data that will be used. The service method in question needs to be transactional.
#Service
public class MyService {
private MyRepository myRepository; // This needs to be a real repository
private OtherService otherService; // This needs to be mocked
#Autowired
public MyService(MyRepository myRepository, OtherService otherService) {...}
#Transactional
public void methodToTest() {
// Get (mock) data from otherService, and interact with (real) myRepository
}
}
I can get it working in my test by manually creating the service with its dependencies. However, the resulting object is just a POJO that has not been enhanced by Spring to be Transactional:
#SpringBootTest
#ContextConfiguration(initializers = {TestContainersConfig.class})
public class MyServiceTest {
#Autowired
MyRepository myRepository;
OtherService otherService;
MyService myService;
#BeforeEach
void setup() {
otherService = Mockito.mock(OtherService.class);
// This works, except that the object is a POJO and so #Transactional doesn't work
myService = new MyService(myRepository, otherService);
}
#Test
#Transactional
void testMethodToTest() {
// Provide mock data
when(otherSerivce.something()).thenReturn(...);
// Run the test
myService.methodToTest();
// Would like to remove these lines: myService should already do it
TestTransaction.flagForCommit();
TestTransaction.end();
// assertions...
}
}
How can I get Spring to enhance the test MyService instance so that methodToTest will handle the transaction?
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 several repositories extending JpaRepository. Now I want to test the custom queries I added in a unit test using a real instance of an H2 database (not in-memory - so that I can inspect the database using the web console).
However, auto-wiring the repository in the unit does not work, I always get a NoSuchBeanDefinitionException: No qualifying bean of type ... UserRepository available.
The code of the repository and the unit test is listed below. Thanks for any help!
UserRepository
public interface UserRepository extends JpaRepository<User, Long> {}
UserRepositoryTests
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest
#ContextConfiguration(classes = TestConfiguration.class)
#Transactional
public class UserRepositoryTests {
#Autowired
private UserRepository userRepository;
#Test
#Commit
public void test() throws AESEncryptionException {
User user = new User().setFirstName("ABC").setLastName("XYZ");
user = userRepository.save(user);
assertNotNull(user.getId());
}
}
TestConfiguration
#Configuration
#EnableAutoConfiguration
#ComponentScan
#PropertySource("classpath:/application.properties")
public class TestConfiguration {}
application.properties
spring.datasource.url = jdbc:h2:file:./db/app-data
spring.datasource.driverClassName = org.h2.Driver
spring.jpa.database-platform = org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto = update
spring.h2.console.settings.web-allow-others = true
EDIT:
As suggested by Mensur Qulami, removing #ContextConfiguration(classes = TestConfiguration.class) did the trick, since this is already covered by #SpringBootTest.
You could use DataJpaTest in combination with #AutoConfigureTestDatabase(replace=Replace.NONE) to use your 'real' database
#RunWith(SpringRunner.class)
#DataJpaTest (showSql = true )
#AutoConfigureTestDatabase(replace=Replace.NONE)
public class UserRepositoryTests {
#Autowired
private UserRepository userRepository;
....
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.