We have Spring application unit tested using JMockit mock framework. Now we would like to write new tests in Kotlin using MockK. Almost everything seems to work fine but we can't figure out how to mock beans autowired by Spring. With JMockit we used to use #Capturing annotation that extended the mocking also on classes implementing the mocked interface. How can I achive similar behavior of a mock in the MockK framework?
Bringing oleksiyp comments to an answer
Currently, Mockk doesn't have that kind of behavior. Its support to Spring is limited, but there's a workaround using Spring itself:
You can create a bean however you want, even in Integration Tests. When creating a bean, you can instantiate a mock:
#Bean
fun bean(): BeanType = mockk()
Then, when this bean is autowired, it will return the mocked instance, and you'll be able to set it's behavior using Mockk's DSL as usual.
Spring documentation recommends that all your components be autowired through the constructor. If you follow that convention you wouldn't have this problem.
To be specific, the recommendation is as following...
#RestController
class SomeRandomController(
#Autowired private val ARepository: aRepository,
#Autowired private val BRepository: bRepository,
#Autowired private val CRepository: cRepository
){ etc ...}
Then in your test you will need the following lines:
val aRepository = mockk<ARepository>(relaxed = true)
val bRepository = mockk<BRepository>(relaxed = true)
val cRepository = mockk<CRepository>(relaxed = true)
val controller = SomeRandomController(aRepository, bRepository, cRepository)
Related
I would like to use Spring #Retryable annotation with #Configurable class. But it seems doesn't work. Is there any way to do it?
I have a legacy dao class which is out of Spring context (because it is used in classes out of Spring context). Some of data access calls are not reliable thus it was decided to give a try to #Retryable. Here an example:
#Configurable
public class SomeLegacyDao {
#Autowired
private JdbcTemplate jdbcTemplate;
#Retryable(value = DataAccessException.class,
maxAttempts = 2, backoff = #Backoff(delay = 100))
public int count() {
return jdbcTemplate.queryForObject(...);
}
}
I can't use #Component/#Repository right now because of the way the other code instantiates dao:
SomeLegacyDao dao = new SomeLegacyDao();
As far as I understand the life cycle of spring it is not possible. When looking into the life cycle of spring (https://howtodoinjava.com/spring-core/spring-bean-life-cycle/ ) it starts with loading the Configuration. Classes annotated with #Configuration are used to instantiate Beans. The #Retryable annotation is, as far as i know, used by the Proxys that Spring creates around beans. You therefore already require a setup Context which can not be done before the configuration is loaded.
If you provide a example/reason for your use case there might be a different approach to your Problem :)
I have created a set of custom events for my application
sealed class myEvent(open val id: Int) {
data class myBigEvent(override val id : Int) : myEvent(id)
data class myIntermediateEvent(override val id: Int): myEvent(id)
}
I have a service that has a method for listening my custom events
#Service
class MailService(
private val otherService: OtherService
) {
#EventListener(myEvent.myBigEvent::class, myEvent.myIntermediateEvent::class)
#Async
fun handleProcessEvent(event: myEvent) {
if (event.id != 10 && otherService.hasCompleted) {
sendMail(event)
}
}
The interesting point is that IntellIj annotates with the eventlistener icon next to the method defintion. When clicking on it I get redirected to all my invocations of publisher.publishEvent(myBigEvent) or publisher.publishEvent(myIntermediateEvent)
The issue is when I try to test it. I have the following setup
#TestExecutionListeners
class myEventTest {
#Autowired
private lateinit var publisher: ApplicationEventPublisher
#Mock
private lateinit var mailService: MailService
#BeforeClass
fun start() {
publisher = ApplicationEventPublisher {}
MockitoAnnotations.initMocks(this)
}
#Test
fun `should receive cmlaProcessEvent published event`() {
val cmlaProcessEvent = JobProcessEvent.CmlaProcessSimulationProcessEvent(mlaSimulationRun)
this.publisher.publishEvent(cmlaProcessEvent)
verify(mailService, times(1)).handleProcessEvent(cmlaProcessEvent)
}
}
I get 'Wanted but not invoked' ... 'Actually, there were zero interactions with this mock.'
I think my issue is with the ApplicationEventPublisher that is not sending the events to my MailService under this test context ?
Remove #TestExecutionListeners, since the default listeners should suffice.
If you're using Spring Boot, you should use #MockBean instead of #Mock.
In any case, you have to actually instruct JUnit to use Spring's testing support.
I assume that you are using JUnit 4, since I see #BeforeClass in the example. So I'm basing the following on that assumption (and using Java syntax instead of Kotlin).
You can instruct JUnit 4 to use Spring's testing support via #RunWith(SpringRunner.class). Note, however, that you'll also need to specify where your ApplicationContext configuration is.
For Spring Framework, you can do that with #ContextConfiguration. For Spring Boot, you'll likely want to use #SpringBootTest. Consult the documentation for testing support in Spring Framework and Spring Boot for details.
As a side note, you can also test ApplicationEvent publication without using a mock by using Spring Framework's built-in testing support for application events.
I am trying to test a class like
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
#SpringBootTest(classes = [TestConfig::class])
#ExtendWith(MockitoExtension::class)
class TotalCalculatorTests {
#Mock
lateinit var jdbcRepo: JDBCRepo
#Mock
lateinit var userCache : LoadingCache<ApproverLevel, List<Approver>>
#InjectMocks
lateinit var calculator: TotalCalculator
#Test
fun totalOrder() {
// val calculator = TotalCalculator(userCache,jdbcRepo)
calculator.total(ItemA(),ItemB())
verify(jdbcRepo).getTotal()
}
}
I get an error Actually, there were zero interactions with this mock. but if I uncomment the // val calculator = TotalCalculator(userCache,jdbcRepo) it works. I assumed mockito would be using the mocks from the test class but that appears to not be true. How can I get the instance of JDBCRepo being used by the #InjectMocks?
It looks like you would like to run the full-fledged spring boot test with all the beans but in the application context you would like to "mock" some real beans and provide your own (mock-y) implementation.
If so, the usage of #Mock is wrong here. #Mock has nothing to do with spring, its a purely mockito's thing. It indeed can create a mock for you but it won't "substitute" the real implemenation with this mock implementation in the spring boot's application context.
For that purpose use #MockBean annotation instead. This is something from the spring "universe" that indeed creates a mockito driven mock under the hood, but substitutes the regular bean in the application context (or even just adds this mock implementation to the application context if the real bean doesn't even exist).
Another thing to consider is how do you get the TotalCalculator bean (although you don't directly ask this in the question).
The TotalCalculator by itself is probably a spring been that spring boot creates for you, so if you want to run a "full fledged" test you should take the instance of this bean from the application context, rather than creating an instance by yourself. Use annotation #Autowired for that purpose:
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
#SpringBootTest(classes = [TestConfig::class])
#ExtendWith(MockitoExtension::class) // you don't need this
class TotalCalculatorTests {
#MockBean
lateinit var jdbcRepo: JDBCRepo
#MockBean
lateinit var userCache : LoadingCache<ApproverLevel, List<Approver>>
#Autowired // injected by the spring test infrastructure
lateinit var calculator: TotalCalculator
#Test
fun totalOrder() {
// val calculator = TotalCalculator(userCache,jdbcRepo)
calculator.total(ItemA(),ItemB())
verify(jdbcRepo).getTotal()
}
}
Now, all this is correct if your purpose to indeed running the full microservice of spring boot and make an "integration" testing
If alternatively you just want to check the code of your calculator, this might be an overkill, you can end up using mockito and plain old unit testing. In this case however you don't need to even start the spring (read create an application context) during the test, and of course there is no need to use #SpringBootTest / #MockBean/#Autowired.
So it pretty much depends on what kind of test is really required here
I'm currently in the process of writing tests for my controllers in a Spring Boot project that uses WebSockets.
As information on the subject is hard to come by, my only lead is this example recommended by the docs.
Allow me to attempt to explain my forays so far, trying to understand and get my test environment set up. I'm following the context-based approach, and I'm torn between #WebMvcTest and #ContextConfiguration (which the example uses).
My motivation behind using #WebMvcTest at all was this line from the Spring Boot docs:
When testing Spring Boot applications, this [using #ContextConfiguration(classes=…) in order to specify which Spring #Configuration to load, or using nested #Configuration classes within your test] is often not required. Spring Boot’s #*Test annotations search for your primary configuration automatically whenever you do not explicitly define one.
#WebMvcTest thus seems particularly suited to the task as it focuses only on the web layer by limiting the set of scanned beans to only those necessary (e.g. #controller) rather than spinning up a complete ApplicationContext.
The code example I'm following uses field injection to initialize channel interceptors to capture messages sent through them.
#Autowired private AbstractSubscribableChannel clientInboundChannel;
#Autowired private AbstractSubscribableChannel clientOutboundChannel;
#Autowired private AbstractSubscribableChannel brokerChannel;
As far as I can tell, these fields are what makes the presence of the TestConfig class (see code block at the end for full class definition) in the example necessary, since without it, I get an error saying no beans qualify as autowire candidates. I believe these two fields in TestConfig are the key:
#Autowired
private List<SubscribableChannel> channels;
#Autowired
private List<MessageHandler> handlers;
However, without #ContextConfiguration(classes = [WebSocketConfig::class]) (WebSocketConfig being my own WebSocket configuration file), these two fields are always null resulting in errors.
So far, this would mean that #ContextConfiguration(classes = [WebSocketConfig::class]) in combination with the presence of TestConfig are needed.
The interesting thing is that without #WebMvcTest, clientInboundChannel, clientOutboundChannel, and brokerChannel are never actually initialized. So what this leaves me with is that I need both #WebMvcTest and #ContextConfiguration, which somehow seems odd.
And with the last update to the example repo being more than two years old, I can't shake the feeling that it may be somewhat outdated.
This is what my test class (Kotlin) currently looks like. I've omitted the createRoom test case for brevity:
#WebMvcTest(controllers = [RoomController::class])
#ContextConfiguration(classes = [WebSocketConfig::class, RoomControllerTests.TestConfig::class])
class RoomControllerTests {
#Autowired
private lateinit var clientInboundChannel: AbstractSubscribableChannel
#Autowired
private lateinit var clientOutboundChannel: AbstractSubscribableChannel
#Autowired
private lateinit var brokerChannel: AbstractSubscribableChannel
private lateinit var clientOutboundChannelInterceptor: TestChannelInterceptor
private lateinit var brokerChannelInterceptor: TestChannelInterceptor
private lateinit var sessionId: String
#BeforeEach
fun setUp() {
brokerChannelInterceptor = TestChannelInterceptor()
clientOutboundChannelInterceptor = TestChannelInterceptor()
brokerChannel.addInterceptor(brokerChannelInterceptor)
clientOutboundChannel.addInterceptor(clientOutboundChannelInterceptor)
}
#Test
fun createRoom() {
// test room creation
// ...
}
#Configuration
internal class TestConfig : ApplicationListener<ContextRefreshedEvent?> {
#Autowired
private val channels: List<SubscribableChannel>? = null
#Autowired
private val handlers: List<MessageHandler>? = null
override fun onApplicationEvent(event: ContextRefreshedEvent) {
for (handler in handlers!!) {
if (handler is SimpAnnotationMethodMessageHandler) {
continue
}
for (channel in channels!!) {
channel.unsubscribe(handler)
}
}
}
}
}
The answer posted here was my first clue towards starting to understand how #WebMvcTest and #ContextConfiguration are meant to be used.
It would appear the line that is really necessary is #ContextConfiguration(classes = [WebSocketConfig::class]), since without it, the necessary configuration (my own WebSocketConfig, in this case) can't be found.
While #WebMvcTest (and test slices in general) does automatically search for the primary configuration, the configuration defined by the main application class is the one that's found since that class is the one annotated with #SpringBootApplication.
What's more, test slices such as #WebMvcTest flat out exclude #configuration classes from scanning, meaning they won't be included in the application context loaded by the test slice.
Supporting this is the fact that the test works if #WebMvcTest is replaced with #SpringBootTest — even without #ContextConfiguration(classes = [WebSocketConfig::class]) — meaning that WebSocketConfig was loaded.
To summarize, as I see it, there are 3 ways to include a non-primary configuration in the application context so that it's detected in integration tests:
Annotate the test class with #SpringBootTest (and optionally #ContextConfiguration to restrict the number of loaded beans to only those needed)
Annotate the test class with a test slice (e.g. #WebMvcTest) in conjunction with #ContextConfiguration
Annotate the test class with a test slice (e.g. #WebMvcTest) and define the configuration in the main class (i.e. the one annotated with #SpringBootApplication)
I have a Spring Boot application and Service with private DAO field inside it. Private DAO property is annotated with #Autowired (no setters or constructors set it, just annotation).
I tried to write Spock test for service, but can't find how to inject mock DAO into #Autowired variable.
class TestService extends Specification {
DAO dao = Mock(DAO)
Service service = new Service()
def "test save"() {
when:
service.save('data')
then:
1 * dao.save('data')
}
}
Any ideas?
UPD: I'm testing java code.
As result I did this:
class TestService extends Specification {
DAO dao = Mock(DAO)
Service service = new Service()
void setup() {
service.dao = dao
}
def "test save"() {
when:
service.save('data')
then:
1 * dao.save('data')
}
}
One point was to use reflection. But Groovy can set private fields directly without additional manipulations. It was news for me.
sorry to bring a little over a year old thread to life but here is my two cents. Groovy does provide access to private fields even though it break encapsulation. Just incase if you haven't figured it out, when you manually instantiate a class with Autowired fields, Autowired fields will be null. You can either provide setters for it and set them or groovy can see private fields anyways. However, if you have luxury I would suggest to refactor it to use constructor injection and do the same for any of your code in future. Field Injection and setter injections have some problems when it comes to testing.