I am trying to write a unit test for a static method which takes a class and method name and does some reflection to call the method with arguments and store the results. I'm using spring-boot.
My test actually works when I run the full suite, but when I run the test as standalone it fails. The problem is that I've created a mock class (a hand written mock, not using mockito or easymock) which I want the static method to use. However, the reflection can not detect my mock class because the class has not been loaded into the applicationContext by spring-boot. Here is the line that fails:
T proxy = SpringApplicationContext.getBean(clazz);
SpringApplicationContext definition:
#Component
public class SpringApplicationContext implements ApplicationContextAware
{
private static ApplicationContext applicationContext_;
#Override
public void setApplicationContext(ApplicationContext applicaitonContext) throws BeansException {
applicationContext_=applicaitonContext;
}
public static <T> T getBean(Class<T> requiredType) throws beanException {
return applicationContext_.getBean(requiredType);
}
*note, I had to retype by hand, please assume obvious syntax errors are typos.
so basically my applicationContext is not being set or defined. I only need one mock bean in the applicationContext, I could do it by hand, but is there a more spring approach using annotations?
It turns out that my test didn't work rather the were run stand alone or part of a suite, I had a separate issue with using the wrong annotations for #BeforeTest which masked the defect when running the whole suite.
The fix was pretty simple. I added the SpringApplicationConfiguration annotation above my test:
#SpringApplicationConfiguration(classes =
{
MockController.class,
SpringApplicationContext.class
}
public class MyTest extends AstractTestNGSpringContextTests
There are two parts to this. The #SpringApplicationCOnfiguration loads only those values I listed. I could have pointed to configuration classes, but that would ultimately load most of the beans in my enviroment which is overkill for a unit test. So I load the two #component objects needed in my ApplicationContext for my unit test to work only.
I also had to extend AbstractTestNGSpringContextTests because It's the only way to get spring to play nice with the TestNG kit were using for our tests. If others are using junit tests instead of TestNG don't extend the AbstracTestNGSpringContextTests, instead I believe your want to add the annotation:
#RunWith(SpringJUnit4ClassRunner.class)
Though I haven't used it since I'm not using junit.
Hopefully this answer saves others who are trying to figure out how to load only a few classes instead of the entire enviroment (most examples I found want you to load configuration files that will load every bean, which is slow and honestly undesirable in a unit test).
Arguably I should still have mocked out the SpringApplicationContext entirely, I'm lazy and sloppy :)
Related
I have just taken over maintenance of an app that has this as its only test: (Yes I know)
#ActiveProfiles("test")
#RunWith(SpringRunner.class)
#RequiredArgsConstructor
#SpringBootTest(classes = App.class)
#MockBean(ElasticSearchIndexService.class)
public class AppStartTest {
#Test
public void contextLoads() {
final var app = new App();
assertNotNull("the application context should have loaded.", app);
}
}
Apart from the fact that there is no automated testing in place is this a good way to test if a Spring boot application loads its context? I would have thought that a simple
assertTrue(true); in the test would suffice, as the context should be loaded no matter what. Why would I want to create another copy of the application? (I sadly could not find anything related to this during my google searches)
There is also the fact that it has both #RunWith(SpringRunner.class) and #SpringBootTest. The test currently "runs" properly but I would expect that this leads to some unexpected behaviour does it not? I found this SO answer talking about it but it does not go into depth why (or if ever) one should use both annotations.
Lastly I already removed the #RequiredArgsConstructor because I don't really know why it is there in the first place. In my humble opinion it does not really serve a purpose.
I am not certain if this fits SO but I was rather curious as I consider myself somewhat of a beginner Spring developer and maybe the previous dev knew more than me
is this a good way to test if a Spring boot application loads its context?
#M.Deinum already answered this
No it isn't. You should #Autowire your App or rather ApplicationContext and check that.
but for anyone looking for a code example it would look like this:
#Autowired
ApplicationContext applicationContext;
#Test
public void contextLoads() {
assertNotNull(applicationContext);
}
This checks if the Application Context was indeed initialized.
There is also the fact that it has both #RunWith(SpringRunner.class) and #SpringBootTest.
Is a remnant of this test initially using JUnit4, where this was indeed necessary. With JUnit5, which I am using the #RunWith(SpringRunner.class) can be left out.
I still am unsure why the #RequiredArgsConstructor annotation was ever needed but the test runs with and without it so I have removed it.
I'm trying to execute a unit test for a service class that has an #Async("asyncExecutor") annotated method. This is a plain JUnit test class with no Spring runners and no intention of using Spring at all in the unit test. I get the exception,
BeanFactory must be set on AnnotationAsyncExecutionAspect to access qualified executor 'asyncExecutor'
Where asyncExectuor is the name of the bean to be used during normal execution. My configuration class looks like this and I solved that previous error message at runtime by adding the mode = AdviceMode.ASPECTJ portion. This service works at runtime without issue in an Async way.
#Configuration
#EnableAsync(mode = AdviceMode.ASPECTJ)
public class AsyncConfiguration {
#Bean(name = "asyncExecutor")
public Executor asyncExecutor() {
...
}
}
I don't understand why the Spring context is being constructed at all in the unit test. The test class is simply annotated #Test on the methods with no class annotations and no mention of Spring. I was hoping to unit test this service class method as a regular method ignoring the async nature, but the annotation is being processed for some reason.
I'm contributing to a much larger gradle + Spring 4 project that I'm not fully knowledgeable about. Is there anything I should be looking for to see if a Spring context is being created by default for all tests?
As you noticed, Spring context is not loaded, that is the reason of your error. Try to initialize Spring context in your test by adding #RunWith and #ContextConfiguration annotations
I came across TestPropertyValues, which is briefly mentioned in the Spring Boot docs here: https://github.com/spring-projects/spring-boot/blob/2.1.x/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc#testpropertyvalues
It's also mentioned in the Migration Guide here: https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Migration-Guide#environmenttestutils
Both examples show an environment variable to apply the properties to, but there's no other documentation that I could find.
In my tests the property setting comes too late to affect the property injection (via #Value) for a Spring Bean. In other words, I have a constructor like this:
public PhoneNumberAuthorizer(#Value("${KNOWN_PHONE_NUMBER}") String knownRawPhoneNumber) {
this.knownRawPhoneNumber = knownRawPhoneNumber;
}
Since the above constructor is called before the test code has a chance to run, there's no way change the property via TestPropertyValues in the test before it's used in the constructor.
I understand that I can use the properties parameter for #SpringBootTest, which updates the environment before beans get created, so what's the appropriate usage of TestPropertyValues?
TestPropertyValues isn't really designed with #SpringBootTest in mind. It's much more useful when you are writing tests that manually create an ApplicationContext. If you really want to use it with #SpringBootTest, it should be possible to via an ApplicationContextInitializer. Something like this:
#RunWith(SpringRunner.class)
#SpringBootTest
#ContextConfiguration(initializers = PropertyTest.MyPropertyInitializer.class)
public class PropertyTest {
#Autowired
private ApplicationContext context;
#Test
public void test() {
assertThat(this.context.getEnvironment().getProperty("foo")).isEqualTo("bar");
}
static class MyPropertyInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
TestPropertyValues.of("foo=bar").applyTo(applicationContext);
}
}
}
Spring Boot's own test make use of TestPropertyValues quite a bit. For example, applyToSystemProperties is very useful when you need to set system properties and you don't want them to be accidentally left after the test finishes (See EnvironmentEndpointTests for an example of that). If you search the codebase you'll find quite a few other examples of the kinds of ways it usually gets used.
I am trying to unit test my Service classes that looks similiar to this:
#Service
public class SomeQueryService {
private final SomeRepository repository;
public SomeQueryService(SomeRepository repository) {
this.repository = repository;
}
public void doSomething() {
// code doing some magic
}
}
SomeRepository is simple repository interface extending JpaRepository interface.
What I want to do is unit test this service to verify whether it is working properly.
I do not want to use mockito to mock repository behaviour instead, I want to have some in-memory implementation (on list or map) that will imitate database behaviour.
Does Spring provide such fake implementations?
I want to avoid making Stub Implementation of such repository by myself as I will be using such tests in many other places.
RealLifeDeveloper has created an MIT-licensed helper-class to do just what you want: implement the repository-interface with a plain-old-java-object that just wraps a Collection, and called it "InMemoryJpaRepository". You will still have to implement some logic yourself1, though it should be easy if your queries are not too complicated.
An article explaining how to do this with example: https://reallifedeveloper.com/creating-in-memory-versions-of-spring-data-jpa-repositories-for-testing/
The repository (which includes other stuff, too) is on github: https://github.com/reallifedeveloper/rld-build-tool
The specific helper-files for creating the inmemory-db are found at https://github.com/reallifedeveloper/rld-build-tools/tree/master/src/main/java/com/reallifedeveloper/tools/test/database/inmemory if you dont want the whole repo.
1 The rule "Don't put logic in tests" exists for a reason and is clearly violated here. However, the well-known and widely-used alternatives mentioned by the other answers, H2-testing and extensive mocking, have their drawbacks too.
The type of testing you are referring to is called "Integration Testing" or "End to end testing" since it tests the whole application or a big chunk of it compared to unit tests that test only one method.
https://www.guru99.com/unit-test-vs-integration-test.html
You should not unit test your repositories, since they are already well tested by the spring team.
Solution:
You can create a test that starts the whole spring container using Spring Boot:
Just create a class in your test folder and annotate it with:
#RunWith(SpringRunner.class)
#SpringBootTest
public class MyTestClass {
#Test
public void test() {
}
}
You can then configure an embedded database using H2 so that your test does not use the production database, just follow the Spring Boot Database Initialization doc.
https://docs.spring.io/spring-boot/docs/current/reference/html/howto-database-initialization.html
PS. You can also create a specific profile using the following annotation on your test class:
#ActiveProfiles("test")
No Spring Data does not provide a fake implementation of that interface.
You'll have to create it on your own or use a mocking framework like Mockito.
I have a very basic scenario where I just need to call a method which has an annotation. This annotation simply calls an AspectJ advice. I just need to make sure that the advice is being called, ideally via a mock verify. Tests are being run using TestNG and mocking using Mockito. Spring is version 4.
class under test
public class MyClassUT
{
#MyAnnotation
public myMethod...
{
...
}
}
test class
#ContextConfiguration(classes = {SpringTestConfig.class})
#WebAppConfiguration
public class MyClassUtTest extends AbstractTestNGSpringContextTests
{
#InjectMocks private MyClassUT mine;
#BeforeMethod
public void init()
{
MockitoAnnotations.initMocks(this);
}
#Test
public void testMyMethod()
{
mine...
}
}
The problem is that the advice is being called and everything is OK, except for the fact that the advice class is instantiated once by spring and another time before calling the said method. The instance being used is the latter which of course has no dependencies injected so it fails. What I am trying to do is provide spring with a mock of my advice or at least inject a mock of the service it depends on and ask AspectJ to use that existing instance.
I have tried using factory methods for the advice, spring test configurations etc, however nothing seems to be working. I have tried also with EnableAspectJautoproxy to no avail, instantiated the aspect with a #Bean annotation, also as a factory method - but nothing works well unfortunately.
(It is also interesting to note that when I enable AspectJ in eclipse, the aspect test also run in maven and as far as I know, nothing changes in pom.xml.)
So, my question is:
How do I make the test use an instance of the aspect I or spring create so that when the method MyMethod is called, its dependencies are in place , or the mock version is used?
This problem is basically equivalent,
but
How do I do this without a single line of XML config - I've seen using an apectOf factory method config being mentioned a lot, but I need a pure annotation solution, if possible;
Works with TestNg not JUnit;
Thank you!