I would like to learn why inner beans are not created while trying to test like below :
RunWith(SpringRunner.class)
#SpringBootTest(classes=MyTest.class)
public class MyTest {
#SpyBean A a;
#Test
public void myTest() {
assertTrue(a.some());
}
#Component
class A {
private B b;
A(B dependency) {
this.b = dependency;
}
boolean some() {
return b.value();
}
}
#Configuration
class B {
boolean value() { return true; }
}
}
Error: No qualifying bean of type 'com.example.MyTest$B' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations:
Despite annotating the inner class with #Configuration it is not creating the bean while testing the method.
please note it works when I add like below #SpringBootTest(classes=MyTest.class,MyTest.B.class,MyTest.A.class})
Add #ContextConfiguration(classes = MyTest.B.class) to the MyTest class.
But
putting configuration into a test class isn't the best idea. It's better to create separate configuration class MyTestConfig that create all need beans for test and use it in the test class by #ContextConfiguration(classes = MyTestConfig.class).
Use #MockBean. And add behavior in #Test:
#SpringBootTest
#AutoConfigureMockMvc
#RunWith(SpringRunner.class)
public abstract class IntegrationTest {
#MockBean
A a;
#Test
public void mySuperTest(){
Mockito.when(a.getById(Mockito.any())).thenReturn(someInstance);
Assert.assertEquals(a.getById("id"), someInstance);
}
}
Related
I'm having some difficulty in writing some Integration tests. I have something like so
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Service.class)
public class ServiceTest {
#Rule
public PostgreSQLContainer postgres = new PostgreSQLContainer();
#Autowired
Service Service;
#Before
public void setUp() {
PostgresqlConnectionFactory connectionFactory = new PostgresqlConnectionFactory(PostgresqlConnectionConfiguration.builder()
.host(postgres.getHost())
.port(postgres.getFirstMappedPort()) // optional, defaults to 5432
.username(postgres.getUsername())
.password(postgres.getPassword())
.database(postgres.getDatabaseName()) // optional
.build());
Resource resource = new ClassPathResource("sql.sql");
Mono<PostgresqlConnection> mono = connectionFactory.create();
mono.map(connection -> connection
.createStatement(Helpers.asString(resource))
.execute()).block();
}
#Test
public void test() {
Request Request = new Request();
request.setName("name");
Mono<Item> itemMono = Service.createNewHub(hubRequest);
Item item = itemMono.block();
Assert.assertEquals(1L, 1L);
}
}
And my Service.class looks like the below
#Service
public class Service {
private Repository repository;
public Service(Repository repository) {
this.repository = repository;
}
public Flux<Item> getAllItems() {
return repository.findAll();
}
}
And my repo
#Repository
public interface Repository extends ReactiveCrudRepository<Item, Integer> {
}
My error is the following
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.Repository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
While all of the code I have written in the application is able to be injected fine, when it comes to the ReactiveCrudRepos, I am not having any luck on getting their instantiated object. What do I need to do to have the implementations created and injected?
As long as you use #SpringBootTest(classes = Service.class), the other beans are not loaded into the application context. The annotation element class of the annotation is described as:
The component classes to use for loading an ApplicationContext.
Remove the element and use the #SpringBootApplication as is:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Service.class)
public class ServiceTest { /* code */ }
Remember, using this way is testing an application context completely different from what will be run on the production environment. It should be a last resort.
Moreover, avoid naming the interface Repository when there exists the Spring annotation #Repository itself. I would personally prefer:
#Repository
public interface ItemRepository extends ReactiveCrudRepository<Item, Integer> {
}
I've upgraded from Spring Boot 1.5 to Spring Boot 2.1.8. I had some tests that were working but are now failing.
I also was using maven-surefire plugin at version 2.9 and it worked, but I upgraded that to 2.22.0 as well, if that matters.
#ExtendWith(SpringExtension.class)
#WebMvcTest(value = ElementController.class, secure = false)
#ContextConfiguration(classes = TestSite1Config.class)
public class ElementControllerSite1IT {
#Autowired
protected MockMvc mvc;
#MockBean
ElementService elementService;
#BeforeEach
public void setup() {
when(elementService.getElementTable( ... )) //skipping args for brevity
.thenReturn(new ElementTable());
}
#Configuration
public static class TestSite1Config {
#Bean
#Autowired
public ElementController elementController(final ElementService elementService) {
return new ElementController(elementService, new ElementControllerProperties(DeploymentLocation.SITE1));
}
#Test
public void failSite1ValidationWithoutId() throws Exception {
ElementParameters params = getParams(false);
mvc.perform(post("/element")
.contentType(JSON)
.andExpect(status().isBadRequest());
}
//more tests, but doesn't matter.
}
There's another class like above, but replace Site1 with Site2.
There is an ElementController & Service class as well.
I get this exception:
Caused by BeanDefinitionOverrideException: Invalid bean definition with name 'elementController' defined in class path resource [ui/v2/web/ElementControllerSite1IT$TestSite1Config.class]: Cannot register bean definition [Root bean: class [null]; ... defined in class path resource [ui/v2/web/ElementControllerSite1ITConfig.class] for bean 'elementController': There is already [Generic bean: class [ui.v2.web.ElementController]; .. defined in file [...ui/v2/web/ElementController.class]] bound.
I didn't write the tests, it's code that I've inherited, in a code base that I'm just getting spooled up on.
You could try #TestPropertySource(properties ="..." :
#ExtendWith(SpringExtension.class)
#WebMvcTest(value = ElementController.class, secure = false)
#ContextConfiguration(classes = TestSite1Config.class)
#TestPropertySource(properties = {"spring.main.allow-bean-definition-overriding=true", "local.server.port=7777"})
public class ElementControllerSite1IT {
...
}
Add spring.main.allow-bean-definition-overriding=true to application.properties
Got it working with this: (for anyone who stumbles upon this question)
#ExtendWith(SpringExtension.class)
#AutoConfigureMockMvc
#WebMvcTest
#ContextConfiguration(classes = {ElementController.class,TestSite1Config.class})
public class ElementControllerSite1IT {
#Autowired
private MockMvc mvc;
...
#Configruation
public static class TestSite1Config {
#Bean
#Primary
public ElementControllerProperties elementControllerProperties() { return ... }
}
...
}
I'm getting this exception on my test excution:
UnsatisfiedDependencyException: Error creating bean with name 'net.gencat.transversal.espaidoc.mongo.GridFSTest': Unsatisfied dependency expressed through field 'resourceProperties'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'net.gencat.transversal.espaidoc.ResourcesConfigProperties' available: expected at least 1 bean which qualifies as autowire candidate.
So, I think message is so clear enough: ResourcesConfigProperties is not satisfied.
My test:
RunWith(SpringRunner.class)
#SpringBootTest()
public class GridFSTest {
#Autowired
private GridFsTemplate gridFsTemplate;
#Autowired
private ResourcesConfigProperties resourceProperties;
public URL getHugeResource() {
try {
return Paths
.get(this.resourceProperties.getHuge())
.toUri()
.toURL();
} catch (MalformedURLException e) {
return null;
}
}
#Test
public void storeHugeFile() throws IOException {
URL resource = this.getHugeResource();
this.gridFsTemplate.store(
resource.openStream(),
resource.getPath(),
"mime"
);
}
}
and ResourcesConfigProperties is:
#ConfigurationProperties(prefix = "files")
public class ResourcesConfigProperties {
private String huge;
/**
* #return the huge
*/
public String getHuge() {
return huge;
}
/**
* #param huge the huge to set
*/
public void setHuge(String huge) {
this.huge = huge;
}
}
into my src/test/resources I have my application.properties file:
files.huge: /home/jcabre/Downloads/1GB.zip
Any ideas?
EDIT
Main Spring boot application:
#SpringBootApplication(
//scanBasePackages = { "cat.gencat.ctti.canigo.arch.web.rs" },
exclude = JmxAutoConfiguration.class
)
#EnableConfigurationProperties({
ApiProperties.class,
FileStoreProperties.class
})
#Import(RedisConfiguration.class)
public class ApiApplication {
public static void main(String[] args) {
SpringApplication.run(ApiApplication.class, args);
}
}
TL;DR:
It is happening, because the #ConfigurationProperties are not managed by the application context you build in tests, although they will be load when the application launches, because you have #EnableConfigurationProperties on your app main class.
#EnableConfigurationProperties on main class only affect the application context you have when you bootRun, but not that in a #SpringBootTest. The application context you build in tests could be, under many circumstances, distinct with that of bootRun, just like in your case.
You can add #Component to make the context be aware of it, both in gradle bootRun and in gradle test. It's the easiest way, but not 100% the suggested way.
More proper way with details if you have time
Instead, you can add #EnableConfigurationProperties({Config1.class, Config2.class}) in a #SpringBootTest, to inject only some of the configuration properties class into the context, to avoid injection overhead.
It would be like:
//#RunWith(SpringRunner.class) // for JUnit4 in Spring
#ExtendWith(SpringExtension.class) // for JUnit5 in Spring.
#SpringBootTest
#EnableConfigurationProperties({
ResourcesConfigProperties.class,
})
#Data
public class ConfigsTest {
#Autowired
private ResourcesConfigProperties resourceConfigProperties;
...
}
Better yet, you can use #SpringBootTest(classes={}): classes within {} are those you want the application context of #SpringBootTest to manage(creation, initialization, loading properties from yaml files, and so on). Then you don't have to load all the context, but only part of it.
You can group all classes of #ConfigurationProperties in one class of #Configuration, and put it in the classes={} of #SpringBootTest, instead of repeating this list of #ConfigurationProperties everywhere. Something like:
//#RunWith(SpringRunner.class) // for JUnit4 in Spring
#ExtendWith(SpringExtension.class) // for JUnit5 in Spring.
#SpringBootTest(classes = {
TestConfiguration.class
})
#Data
public class ConfigsTest {
#Autowired
private ResourcesConfigProperties resourceConfigProperties;
...
}
TestConfiguration.java:
#EnableConfigurationProperties({
ResourcesConfigProperties.class,
})
#Configuration
public class TestConfiguration {
}
You need to add ResourcesConfigProperties to your EnableConfigurationProperties annotation in the main spring boot class, this will load and create a bean out of the ResourceConfigProperties for you
You could also add #Component to your ResourceConfigProperties if you do not want to add it to the EnableConfigurationProperties annotation.
When using the SpringBootTest or any slice test it will use whatever is annotated on, or beans defined within the main SpringBootApplication within the test context.
You also need to annotate ResourcesConfigProperties class with #Configuration as below, otherwise it will not create a bean of this class in the spring container.
#Configuration
#ConfigurationProperties(prefix = "files")
public class ResourcesConfigProperties {
private String huge;
/**
* #return the huge
*/
public String getHuge() {
return huge;
}
/**
* #param huge the huge to set
*/
public void setHuge(String huge) {
this.huge = huge;
}
}
Let's use the following as an example.
#Autowired
#MockBean
private Foo foobar;
Does the Spring Context load the class Foo first, and then apply the mock? Or does the #Mockbean get detected somehow, and Spring creates and applies the mock instead of loading the class Foo into the Spring Context. I have a suspicion that it is the latter, but I would like confirmation.
Spring will throw an exception.
Let's define the class Foo.
#Component
public class Foo {
public Foo() {
System.out.println("I am not a mock");
}
}
Whenever testing using #Autowired, spring injects an instance of Foo and the constructor will print "I am not a mock", as in the code below.
#SpringBootTest(classes = Main.class)
#RunWith(SpringRunner.class)
public class FooTest {
#Autowired
Foo foo;
#Test
public void test() {
System.out.println(foo);
}
}
On the other hand, using #MockBean, spring will not create the real bean and the message in the constructor will not be printed. This scenario is represented by the following code.
#SpringBootTest(classes = Main.class)
#RunWith(SpringRunner.class)
public class FooTest {
#MockBean
Foo foo;
#Test
public void test() {
System.out.println(foo);
}
}
However, when you try to use both annotations together, spring will throw a BeanCreationException caused by an IllegalStateException. It means that the field foo cannot have an existing value. This scenario will happen in executing the code below:
#SpringBootTest(classes = Main.class)
#RunWith(SpringRunner.class)
public class FooTest {
// this will not work
#Autowired
#MockBean
Foo foo;
#Test
public void test() {
System.out.println(foo);
}
}
And the stack trace will be something like:
org.springframework.beans.factory.BeanCreationException: Could not inject field: com.tbp.Foo com.FooTest.foo; nested exception is java.lang.IllegalStateException: The field com.tbp.Foo com.FooTest.foo cannot have an existing value
at org.springframework.boot.test.mock.mockito.MockitoPostProcessor.inject(MockitoPostProcessor.java:413) ~[spring-boot-test-1.5.2.RELEASE.jar:1.5.2.RELEASE]
I have problem with unit test. Below is the sample code snippet. I have mock a bean and inject into #configuration class and use the mocked property to create another bean.
In the below, if i inspect, b.getSomething() returning me the default value like "" for string, 0 for int. etc. I am not getting the mocked value. Any idea how to do?
#Configuration
class A{
#Autowired B b;
#Bean
public SomeClass someBean(){
SomeClass clas = new SomeClass();
clas.setSomething(b.getSomething());
return clas;
}
}
#ContextConfiguration(classes = { A.class}, loader = SpringockitoAnnotatedContextLoader.class)
class ATest{
#ReplaceWithMock
#Autowired
B b;
#Before
public void setup(){
Mockito.when(b.getSomething()).thenReturn("ABC");
}
}
This is the way I create my mocks. Have a Bean which returns the Mock, and autowire it where needed.
#Autowired
MyClass myClassMock;
#Bean
public MyClass getMyClassMock(){
MyClass mock = Mockito.mock(MyClass.class);
Mockito.when(mock.getSomething()).thenReturn("ABC");
return mock;
}