Is it possible to externalize #MockBean definitions, and combine them with composition instead of inheritance?
Because I find myself often repeating mock definitions, and I would rather be able to load/inject them and define them outside of the test class.
Example:
#SpringBootTest
public class Service1Test {
#MockBean
private Service1 service1;
#BeforeEach
public void mock() {
when(service1.call()).thenReturn(result1);
}
}
#SpringBootTest
public class Service2Test {
#MockBean
private Service2 service2;
#BeforeEach
public void mock() {
when(service2.call()).thenReturn(result2);
}
}
#SpringBootTest
public class Service3Test {
#MockBean
private Service1 service1;
#MockBean
private Service2 service2;
#BeforeEach
public void mock() {
when(service1.call()).thenReturn(result1);
when(service2.call()).thenReturn(result2);
}
}
I would like to externalize the mocks somehow, so I could just load them.
Pseudocode as follows:
public class MockService1 {
#MockBean
private Service1 service1;
#BeforeEach
public void mock() {
when(service1.call()).thenReturn(result1);
}
}
#SpringBootTest
#Import({MockService1.class, MockService2.class})
public class Service3Test {
}
Yes, you can achieve it by using an external configuration class. For example:
#TestConfiguration
public class TestConfig {
#Bean
#Primary
public Foo foo() {
return mock(Foo.class); //you can use Mockito or a different approach
here
}
#Bean
#Primary
public Bar bar() {
return mock(Foo.class);
}
}
#Import(TestConfig.class)
public class MyTestClass {
#Autowire
private Foo foo;
#Autowire
private Bar bar;
}
Related
Earlier i had the implemenation like:
public class FeedbackService {
private final FeedbackHelper feedbackHelper;
#Inject
public FeedbackService(FeedbackHelper feedbackHelper) {
this.feedbackHelper = feedbackHelper;
}
//rest of the class
}
Test file
public class FeedbackDataServiceTest {
private FeedbackService feedbackService;
#Mock private FeedbackHelper feedbackHelper;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.feedbackService = new FeedbackService(feedbackHelper);
}
}
It was workign fine. But when I changed to:
public class FeedbackService {
#Inject private FeedbackHelper feedbackHelper;
}
Test file
public class FeedbackDataServiceTest {
private FeedbackService feedbackService;
#Mock private FeedbackHelper feedbackHelper;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
}
The test cases started failing. Is anything else required to be done?
Use #InjectMocks to inject the mocks into the Service class.
public class FeedbackDataServiceTest {
#InjectMocks private FeedbackService feedbackService;
#Mock private FeedbackHelper feedbackHelper;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
}
There are three types of injection. The suggestion is to use constructor injection if the dependency is mandatory and setter injection if it is optional.
You can use #InjectMock on the under test object if you want to mock a field injection. But it is hard to test if you want to test without mocking framework.
#RunWith(MockitoJUnitRunner.class)
public class ApplicationTest
{
#InjectMocks
MainClass sut;
#Mock
DatabaseDAO dependentClassOne;
#Test
public void validateTest()
{
boolean saved = sut.save("abcd");
assertEquals(true, saved);
}
I have a #Service bean that I need static access to:
#Service
public class MyBean implements InitializingBean {
private static MyBean instance;
#Override
public void afterPropertiesSet() throws Exception {
instance = this;
}
public static MyBean get() {
return instance;
}
public String someMethod(String param) {
return "some";
}
}
Usage:
#Service
public class OtherService {
public static void makeUse() {
MyBean myBean = MyBean.get();
}
}
Problem: when I write an integration junit test for OtherService that makes use of the stat MyBean access, the instance variable is always null.
#RunWith(SpringRunner.class)
#SpringBootTest
public class ITest {
#Autowired
private OtherService service;
#MockBean
private MyBean myBean;
#Before
public void mock() {
Mockito.when(myBean.someMethod(any()).thenReturn("testvalue");
}
#Test
public void test() {
service.makeUse(); //NullPointerException, as instance is null in MyBean
}
}
Question: how can I write an integration test when using such type of static access to a spring-managed bean?
If you want to influence the #Bean-creation, then use #Configuration
#Configuration
public class MyConfig {
#Bean
public MyBean myBean() {
return new MyBean;
}
#Bean
public OtherService otherService () {
return new OtherService(myBean());
}
}
Then mocking becomes really easy:
#RunWith(SpringRunner.class)
#SpringBootTest
public class ITest {
#MockBean
private MyBean myBean;
#Autowired
private OtherService service;
#Test
public void test() {
// service is initialised with myBean
...
}
}
When more control is needed, then you can choose the following approach. It provides sometimes more control than just a #MockBean. In your test you can easily mock a method just before calling it. This is my preferred way.
#Configuration
public class MyConfig {
...
#Bean
public MyBean getMyBean() {
return mock( MyBean.class);
}
#Bean
public OtherService otherService () {
return new OtherService( getMyBean());
}
}
In the application you can use #Autorwired to access it AND implement method overrule/mocking easily.
#RunWith(SpringRunner.class)
#SpringBootTest
public class AnotherTest {
#Autowired
private MyBean myBean;
#Autowired
private OtherService service;
#Test
public void test() {
when( myBean.method( a, b)).thenReturn( something);
service.doSomething( ....); // with myBean.method() overruled
}
}
Is it possible to just ignore/mock any injected dependencies inside a MockedBean?
Example:
#Service
public class MyService {
#Autowired
private MailerService mailer;
public void test1() {
//does not use mailer
}
public void test2() {
//...
mailer.send();
}
}
#Service
public class MailerService {
//I want these to be automatically mocked without explicit declaration
#Autowired
private JavaMailSender sender;
#Autowired
private SomeMoreService more;
//also these should be mocked without having to provide properties
#Value("${host}") private String host;
#Value("${user}") private String user;
#Value("${pass}") private String pass;
}
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest
public class MyServiceTest {
#Autowird
private MyService myservice;
#MockBean
private MailserService mailer;
#Test
public void test1() {
myservice.test1();
}
}
I could use #MockBean to sort out mailer injection dependency. But any service inside the mocked bean would also have to be explicitly mocked.
Question: is it possible to mock a service "away". Means, just mock the bean and don't care what's inside the #MockedBean (or automatically also mock anything inside #MockedBean)?
As for me the best way to inject mocks is to use MockitoJUnitRunner
#RunWith(MockitoJUnitRunner.class)
public class MocksTests {
#InjectMocks
private ParentService parent;
#Mock
private InnerService inner; // this will be injected into parent
//your tests
}
I was going through this tutorial which sets up the SUT using a constructor but my question is what if there is no constructor e.g. if we have:
#Autowire private PetRepository petRepository;
#Autowire private VetRepository vetRepository;
#Autowire private OwnerRepository ownerRepository;
#Autowire private VisitRepository visitRepository;
in the service/controller. How we can set this up?
I prefer to annotate the setters instead of the attributes on the classes. For example:
public class SomeClass implements SomeInterface {
private PetRepository petRepository;
private VetRepository vetRepository;
private OwnerRepository ownerRepository;
private VisitRepository visitRepository;
... some methods ...
#Resource
public void setPetRepository(PetRepository petRepository) {
this.petRepository= petRepository;
}
#Resource
public void setVetRepository(VetRepository vetRepository) {
this.petRepository= vetRepository;
}
#Resource
public void setOwnerRepository(OwnerRepository ownerRepository) {
this.ownerRepository = ownerRepository;
}
#Resource
public void setVisitRepository(VisitRepository visitRepository) {
this.visitRepository= visitRepository;
}
}
Then you can create a test case like this with Mockito and Junit:
public class SomeClassTestCase {
#Mock
private PetRepository petRepository;
#Mock
private VetRepository vetRepository;
#Mock
private OwnerRepository ownerRepository;
#Mock
private VisitRepository visitRepository;
private SomeClass someClass;
#Before
public void before(){
MockitoAnnotations.initMocks(this);
someClass = new SomeClass();
someClass.setPetRepository(petRepository);
someClass.setVetRepository(vetRepository);
someClass.setOwnerRepository(ownerRepository);
someClass.setVisitRepository(visitRepository);
}
#Test
public void someTest() {
...
}
}
Hope it helps.
You can use Mockito's #InjectMocks annotation. This will instantiate and then inject the Mock dependencies, for example:
#RunWith(MockitoJUnitRunner.class)
public ServiceTest {
#Mock
PetRepository petRepository
// ...omitted mocks ...
#InjectMocks
ClinicServiceImpl service;
}
See the documentation for further usage examples and caveats.
Is there any way to inject non-mocked objects with #InjectMocks?
My Setup has a UserSignupService with dependencies to a MailService and a UserRepository (a Spring Data Repository). I've a unit test creating a spy of the MailService and I annotated the UserSignupService with #InjectMocks. Sadly this won't inject the UserRepository (non-mocked) into the service class.
Combining #InjectMocks with #Autowired won't inject the mocked MailService, but the bean from the application context.
MockitoAnnotations.initMocks() is run in AbstractServiceTest.setUp(). This class also holds the configuration of the the unit test (SpringJunit4TestRunner, etc.)
public class UserSignupServiceTest extends AbstractServiceTest {
#Autowired #Spy
private MailService<UserActivationMail> mailService;
#InjectMocks
private UserSignupServiceImpl service;
#Before
public void setUp() throws Exception {
super.setUp();
}
}
#Service
public class UserSignupServiceImpl implements UserSignupService {
private final UserRepository repository;
private final MailService<UserActivationMail> mailService;
#Autowired
public UserSignupServiceImpl(UserRepository repository,
MailService<UserActivationMail> mailService) {
this.repository = repository;
this.mailService = mailService;
}
//methods...
}
You need to initialize your Mockito MockitoAnnotations.initMocks(this);
Here is sample Spring JUnit test class I have
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath:config/spring-context.xml" })
public class SpringKPTest {
#Autowired
SomeClassA SomeClassA;
#Mock
SomeClassB SomeClassB;
#Before
public void init(){
MockitoAnnotations.initMocks(this);
}
}