I created a validation constraint depending on a repository.
public class PersonValidator implements ConstraintValidator {
#Autowired
private PersonRepository personRepository;
#Override
public void initialize(PersonValidator personValidator) {
}
#Override
public boolean isValid(Person person, ConstraintValidatorContext context) {
return null != repository.findByName(person.getName());
}
}
Testing the validator itself is easy by mocking the PersonValidator but I want to test the integration with the validator to check the validation message for example.
public class PersonValidatorTest {
#Autowired
private Validator validator;
#Test
public void integration() {
Person person = new Person();
person.setName("person");
Set<ConstraintViolation<Person>> constraintViolations = validator.validate(person);
Assert.assertEquals(0, constraintViolations.size());
}
}
I don't know how to inject a PersonValidator inside the Validator with the mocked repository.
Try running test with SpringJUnit4ClassRunner, and create a mock repository bean and annotate with spring's #Primary annotation or mark as primary in bean definition for test to autowire mock repository.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"/context.xml", "/test-context.xml"})
public class PersonValidatorTest {
#Autowired
private Validator validator;
....
You can create mock repository using mockito factory bean as below
public class MockitoFactoryBean<T> implements FactoryBean<T> {
private Class<T> classToBeMocked;
public MockitoFactoryBean(Class<T> classToBeMocked) {
this.classToBeMocked = classToBeMocked;
}
#Override
public T getObject() throws Exception {
return Mockito.mock(classToBeMocked);
}
....
and then create spring's context file 'test-context.xml' for test repository
<bean id="mockRepository" primary="true" class="com.test.mock.MockitoFactoryBean">
<constructor-arg value="com....PersonRepository"/>
</bean>
try this
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = "context.xml")
public class PersonValidatorTest {
#Autowired
private Validator validator;
...
I prefer setter injection, which makes injecting mocks simple during tests.
Or you can also use reflection based injection with a spring util class.
Related
Is it possible to inject a mock service into a prototype bean using the #Autowired constructor? I realize I could switch to setter injection but I would prefer to use the constructor if possible.
#Component
#Scope(value = "prototype")
public class Prototype {
private DependantService dependantService;
#Autowired
public Prototype(DependantService dependantService) {
this.dependantService = dependantService;
}
}
#SpringBootTest
public class TestPrototype {
#Autowired
private ApplicationContext ctx;
#Mock
private DependantService dependantService;
#Test
public void testPrototype() {
// How can I inject the mock service?
ctx.getBean(Prototype.class);
}
}
Turns out there is an overloaded version of the getBean method that accepts arguments. I would downvote my on question if I could.
#SpringBootTest
public class TestPrototype {
#Autowired
private ApplicationContext ctx;
#Mock
private DependantService dependantService;
#Test
public void testPrototype() {
Prototype p = ctx.getBean(Prototype.class, dependantService);
// Test p
}
}
If you want to speed up your unit tests, [and do true isolated unit testing,] I suggest taking a look at the #InjectMocks mockito annotation. #SpringBootTest fires up the Spring container which is pretty cumbersome.
#Controller
public class MyController {
#Inject
private Logger log;
public methodThatNeedsTesting(){
log.info("hey this was called");
}
}
#TestInstance(Lifecycle.PER_CLASS)
#ExtendWith({ MockitoExtension.class })
class MyControllerTest {
#Mock
private Logger log;
#InjectMocks
private MyController myController;
#Test
void test_methodThatNeedsTesting() throws Exception {
myController.methodThatNeedsTesting();
// myController will not throw an NPE above because the log field has been injected with a mock
}
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
#Mapper(uses = SomeMapper.class,imports = Date.class)
public interface DomainModelMapper {
Model domainToModel(Domain domain);
#Mapping(target="dateUpdated", source="dateUpdated" ,defaultExpression = "java(Date.from(java.time.OffsetDateTime.now().toInstant()))")
#Mapping(target="id.key",source="id.key",defaultExpression = "java(com.datastax.driver.core.utils.UUIDs.timeBased())")
Domain modelToDomain(Model model);
}
I have a mapper class to do some Date conversions
public class SomeMapper {
public Date OffsetDateTimeToDate(OffsetDateTime offsetDateTime) {
return offsetDateTime != null ? Date.from(offsetDateTime.toInstant()):null;
}
public OffsetDateTime DateToOffsetDateTime(Date date) {
return date != null ? date.toInstant().atOffset(ZoneOffset.UTC) : null;
}
}
This is my service class where I use DomainModelMapper
#Service
public class SomeServiceImpl implements SomeService {
#Autowired
someRepository someRepository;
private final DomainModelMapper domainToModelMapper =
Mappers.getMapper(DomainModelMapper.class);
#Override
public Model saveSomething(Model model) {
return DomainModelMapper.domainToModel(someRepository
.save(DomainModelMapper.modelToDomain(model)));
}
How can I unit test saveSomething(Model model) method? How I can inject Mapstruct classes or mock them?
If you make the #Mapper interface as a Spring-based component model, then it can be autowired through #Autowired annotation. Read more at 4.2. Using dependency injection
#Mapper(uses = SomeMapper.class,imports = Date.class, componentModel = "spring")
public interface DomainModelMapper {
// IMPLEMENTATION
}
#Service
public class SomeServiceImpl implements SomeService {
#Autowired
SomeRepository someRepository;
#Autowired
DomainModelMapper domainModelMapper;
// THE REST OF THE IMPLEMENTATION
}
The testing becomes fairly easy since all the beans can be also injected in the #SpringBootTest with the #Autowired annotation.
The DomainModelMapper can be autowired and used in the unit test as is and rely on its implementation
The SomeRepository shall be either mocked using #MockBean which overwrites an existing bean or creates a new one if none of that type exists... or autowired as in the implementation if you use an in-memory database for the testing phase, such as H2.
In any case, the test class will be ready for testing.
#SpringBootTest
public class SomeServiceTest {
#Autowired // or #MockBean
SomeRepository someRepository;
#Autowired // no need to mock it
DomainModelMapper domainModelMapper;
#Test
public void test() {
// TEST
}
}
I have Spring Integration test where I'm trying to Mock some of my Beans. For some reason although I Mocked them they are NULL. Here is code snippet:
The Bean which I want to Mock
#Component
public class MockWS {
public String callSoapClient() throws JAXBException{
return "CallSoapCl";
}
}
The class where the Bean is used
public class SmDpES2PortImpl implements ES2SmDp {
#Autowired
private MockWS mock;
#Override
public void es2DownloadProfile(ES2DownloadProfileRequest parameters) {
try {
LOG.info("\n\n\n TEST BEAN: " + mock.callSoapClient() + "\n\n");
}
}
}
Spring boot integration test where the Bean has been mocked
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT)
#FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class ES2SmDpApplicationTests {
#MockBean(name="mockWS")
MockWS mockService;
#Test
public void test1Es2DownloadProfile_Sucess() throws MalformedURLException, JAXBException, SOAPException {
when(mockService.callSoapClient()).thenReturn("CallMockCLient");
}
}
Output from the build execution: TEST BEAN: null
In my case the following combination of annotations worked:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = { ControllerThatIAmTesting.class })
#AutoConfigureMockMvc(addFilters = false) // if using MockMvc object
But I had to declare explicitly both Autowired objects that I use in the ControllerThatIAmTesting in the test class with #MockBean annotation - otherwise Spring would complain that it cannot find suitable implementation - incidentally both my interfaces and their implementations are in the same corresponding packages
Also, using #WebMvcTest instead of #SpringBootTest (other suggest it as more specific scenario) resulted in Spring failing to find and initialize some other #Autowired dependencies from my #Configuration classes.
Related posts post1 post2 post3
You should mock an interface, not a class. Also, SmDpES2PortImpl must be a Spring bean. Try the following:
Interface:
public interface IMockWS {
public String callSoapClient() throws JAXBException;
}
Component class:
#Component
public class MockWS implements IMockWS {
#Override
public String callSoapClient() throws JAXBException{
return "CallSoapCl";
}
}
Service class:
#Service //Also #Component is a good alternative
public class SmDpES2PortImpl implements ES2SmDp {
#Autowired
private IMockWS mock; //Notice that you are wiring an interface
#Override
public void es2DownloadProfile(ES2DownloadProfileRequest parameters) {
try {
LOG.info("\n\n\n TEST BEAN: " + mock.callSoapClient() + "\n\n");
}
}
}
Test class:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT)
#FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class ES2SmDpApplicationTests {
#MockBean
IMockWS mockService; //Again, you are mocking the interface, not the implementing class
#Test
public void test1Es2DownloadProfile_Sucess() throws MalformedURLException, JAXBException, SOAPException {
when(mockService.callSoapClient()).thenReturn("CallMockCLient");
}
}
I have the following set up:
A sample class that needs to be mocked during testing:
#Component
class MyConfig
{
public String getConfig()
{
return "RealValue";
}
}
An interface that defines a single method and has two implementations:
interface MyInterface
{
String myMethod();
}
class MyImpl1 implements MyInterface
{
private final MyInterface delegate;
private final MyConfig config;
public MyImpl1(final MyInterface delegate, final MyConfig config)
{
this.delegate = delegate;
this.config = config;
}
#Override
public String myMethod()
{
return this.getClass().getSimpleName() + ": " + config.getConfig() + ", " + delegate.myMethod() + ";";
}
}
class MyImpl2 implements MyInterface
{
private final MyConfig config;
public MyImpl2(final MyConfig config)
{
this.config = config;
}
#Override
public String myMethod()
{
return this.getClass().getSimpleName() + ": " + config.getConfig();
}
}
A factory class that prepares a bean of type MyInterface using the MyConfig object that is a spring bean injected into MyFactory:
#Component
class MyFactory
{
#Autowired
private MyConfig config;
// Factory method to create the bean.
#Bean(name = "myInterface")
protected MyInterface myInterface()
{
final MyImpl2 myImpl2 = new MyImpl2(config);
final MyImpl1 myImpl1 = new MyImpl1(myImpl2, config);
return myImpl1;
}
// A simple getter that prepares MyInterface on the fly.
// This is just to demonstrate that getter picks the mock where as
// the factory bean doesn't
public MyInterface getInterface()
{
final MyImpl2 myImpl2 = new MyImpl2(config);
final MyImpl1 myImpl1 = new MyImpl1(myImpl2, config);
return myImpl1;
}
}
A simple test case that checks if the mock version of MyConfig is getting into the bean created using #Bean or not:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {
"classpath:application-context.xml"
})
public class SpringBeanMockExampleTest
{
#Mock
private MyConfig config;
#InjectMocks
#Autowired
protected MyFactory factory;
#Resource
private MyInterface myInterface;
#Before
public void setupMocks()
{
MockitoAnnotations.initMocks(this);
}
/**
* Fails as the return value is "MyImpl1: RealValue, MyImpl2: RealValue;"
*/
#Test
public void testBean()
{
Mockito.when(config.getConfig()).thenReturn("MockValue");
Assert.assertEquals("MyImpl1: MockValue, MyImpl2: MockValue;", myInterface.myMethod());
}
/**
* Assertion passes here.
*/
#Test
public void testGetter()
{
Mockito.when(config.getConfig()).thenReturn("MockValue");
Assert.assertEquals("MyImpl1: MockValue, MyImpl2: MockValue;", factory.getInterface().myMethod());
}
}
I expected the testBean method to pass also, but obviously the mock is not getting injected into the factory bean created in MyFactory.
It seems the mocks are replacing the actual beans after the factory bean creation step is completed. Due to this, the reference in factory beans is not updated with the mock.
How can I fix this so that testBean works as expected?
This won't work.
Your Spring context is initialized first. Afterwards the TestExecutionListeners are executed that handle the dependency injection in your test (e.g. #Autowired).
Then, before each test is run, your #Before method will initialize the Mockito mocks in your test instance of SpringBeanMockExampleTest, effectively overriding the autowired Spring dependencies.
Why? Mockito creates a new instance for all attributes annotated with #InjectMocks, #Mock, #Spy and #Captor.
A possible solution would be to manually set the mocked config in the factory, instead of using #InjectMocks, overriding the Spring config bean.
#Before
public void setupMocks(){
Config config = mock(Config.class);
factory.setConfig(config);
}
Please note that combining mocks with a (Spring) integration tests is bad practice, since mocking should only be done in unit tests.
A better setup would be to setup a separate Config bean in your Spring context using profiles, e.g.:
#Profile("testing")
#Component
public class TestConfig implements Config {
public String getConfig(){
return "testValue";
}
}