I am creating a project utilizing the new Spring configurations. I have a base class which holds a few properties:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {Basic.class, Protected.class})
public class BaseTest {
#Autowired(required = false) protected String userName;
#Autowired(required = false) protected String password;
#Autowired protected String baseURL;
#Test
public void outputData() {
System.out.println("UserName: " + userName + " Password: "
+ password + "Base URL: " + baseURL);
}
}
#ActiveProfiles("default,protected")
public abstract class ProtectedTest extends BaseTest
{
#Autowired protected String userName;
#Autowired protected String password;
}
#Configuration #Profile("default")
public class Basic {
#Bean public String baseURL() { return "http://www.baseURL.com"; }
}
#Configuration #Profile("protected")
public class Protected {
#Bean public String userName() { return "userName"; }
#Bean public String password() { return "password"; }
}
However, when I go to run my protected tests I receive a notification that the base URL is not wired in properly. Since it extends the BaseTest, and has both profiles active, why am I not receiving the baseURL bean?
It has to be #ActiveProfiles({"default","protected"}), in your case it will assume that the profile by name default, protected is active, not default and protected
One more thing is that annotation in base class BaseTest is not derived by the ProtectedTest, so you will again need to put #RunWith and #ContextConfiguration for your test to run
Related
Hi I have this simple code for my Spring Boot Project:
#Component
public class UserRowMapper implements RowMapper<User> {
#Value("${bug.value}")
private String id;
#Value("${wrong.value}")
private String userName;
#Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
return User.builder()
.id(rs.getInt(id))
.userName(rs.getString(userName)).build();
}
}
what I want is to create a simple Mockito Test that will check #Value strings like so:
#ExtendWith(MockitoExtension.class)
class UserRowMapperTest {
#Mock
Environment environment;
#Mock
ResultSet resultSet;
#InjectMocks
UserRowMapper userRowMapper;
#Test
void testMapRow() {
when(environment.getProperty("user.id")).thenReturn("id");
when(environment.getProperty("user.userName")).thenReturn("userName");
try {
final User user = userRowMapper.mapRow(resultSet, anyInt());
// check if its ok
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
But I can't find a simple way to check if the value I injected is what I expect.
any ideas?
Unfortunately, there is no mocking mechanism for Spring's #Value. However, you can use a simple workaround using ReflectionUtils that serves for this purpose according to the JavaDoc:
ReflectionTestUtils is a collection of reflection-based utility methods for use in unit and integration testing scenarios.
There are often times when it would be beneficial to be able to set a non-public field, invoke a non-public setter method, or invoke a non-public configuration or lifecycle callback method when testing code involving
ReflectionTestUtils.setField(userRowMapper, "id", "my-id-value");
ReflectionTestUtils.setField(userRowMapper, "userName", "my-userName-value");
JavaDoc for ReflectionTestUtils#setField(Object, String, Object).
Add getter methods for id and userName fields instead of mocking Environment class.
#Component
public class UserRowMapper implements RowMapper<User> {
#Value("${bug.value}")
private String id;
#Value("${wrong.value}")
private String userName;
#Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
return User.builder()
.id(rs.getInt(getId()))
.userName(rs.getString(getUserName())).build();
}
public String getId() {
return id;
}
public String getUserName() {
return userName;
}
}
While mocking:
Mockito.when(userRowMapper.getId()).thenReturn("id");
Mockito.when(userRowMapper.getUserName()).thenReturn("userName");
Also, you can use TestPropertySource annotation to provide altogether different properties file:
#SpringBootTest
#TestPropertySource(locations = "/application2.properties")
public class TestClassTest {
#Autowired
TestClass testClass;
#Test
public void test() {
assertEquals("id", testClass.getId());
}
}
I would rather suggest to you to do not use inline #Value annotation on the consumer class. As you have seen, the class testability decreases.
You can solve your problem simply creating a #Configuration bean and injecting it to the UserRowMapper class. In this way, using DI you can easily mock the configuration in your tests.
See below a naïve implementation.
#Configuration
public class UserRowMapperConfiguration {
#Value("${bug.value}")
private String id;
#Value("${wrong.value}")
private String userName;
public String getId() {
return id;
}
public String getUserName() {
return userName;
}
}
#Component
public class UserRowMapper implements RowMapper<User> {
private UserRowMapperConfiguration configuration;
public UserRowMapper (UserRowMapperConfiguration configuration) {
this.configuration = configuration;
}
#Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
return User.builder()
.id(rs.getInt(this.configuration.getId()))
.userName(rs.getString(this.configuration.getUserName())).build();
}
}
#ExtendWith(MockitoExtension.class)
class UserRowMapperTest {
#Mock
UserRowMapperConfiguration configuration;
#Mock
ResultSet resultSet;
#InjectMocks
UserRowMapper userRowMapper;
#Test
void testMapRow() {
when(configuration.getId()).thenReturn("id");
when(configuration.getUserName()).thenReturn("userName");
try {
final User user = userRowMapper.mapRow(resultSet, anyInt());
// check if its ok
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
As thepaoloboi suggested use a configuration class to hold all your configs.
Now to test that your config is pointing to the right #Value key, you create an integration test by simply loading that object using spring without loading the whole context. That way it'll be as fast as a unit test.
Here's an example:
#ExtendWith(SpringExtension.class)
#Import(UserRowMapperConfiguration.class)
#TestPropertySource(properties = { "user.id=id" , "user.userName=userName"})
class UserRowMapperConfigurationTest {
#Autowired
UserRowMapperConfiguration userRowMapperConfiguration;
#Test
void test() {
assertEquals("id",userRowMapperConfiguration.getId());
assertEquals("userName",userRowMapperConfiguration.getUserName());
}
}
and Configuration class:
#Configuration
public class UserRowMapperConfiguration {
#Value("${bug.value}")
private String id;
#Value("${wrong.value}")
private String userName;
public String getId() {
return id;
}
public String getUserName() {
return userName;
}
}
i have a question here, please give some ideas.
I have two beans. FaceComparisonServerImpl depends on FaceServer.
When i want to test. I want to change the String in my 'FaceServer' bean.
#Service
public class FaceComparisonServerImpl implements FaceComparisonServer {
#Autowired
private FaceServer faceServer;
#Override
public FaceComparsionInfo getServerInfo() {
String serverInfo = faceServer.getServerInfo();
...
}
}
#Component
public class FaceServer {
#Autowired
private RestTemplate restTemplate;
//Not final, just to test.
private String version = "1.0";
private static final String CODE = "code";
private static final String MESSAGE = "message";
//Final
private static final String SERVER_URL = "http://127.0.0.1:8066/api/ZKComparison";
}
Bellow is my test code.
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = TestConfig.class)
public class FaceServerTestByTyler {
#Autowired
private FaceComparisonServer faceComparisonServer;
#Test
public void getServerInfo(){
//How can i modify the value of SERVER_URL in faceServer?
FaceComparsionInfo serverInfo = faceComparisonServer.getServerInfo();
System.out.println(serverInfo);
}
}
My question is:
How can i modified the value of 'version' and 'SERVER_URL' in #Bean(faceServer)?
Thanks you!
You need create FaceServer mock bean for test configuration.
And override required methods
#Configuration
Class TestConfig{
#Bean
#Primary
public FaceServer faceServer() {
return new FaceServer() {
#override
public String getServerInfo(){
return "required info";
}
};
}
}
The easiest way to customize the values is to make them Spring properties:
#Component
public class FaceServer {
#Value("${faceServer.version}")
private String version;
#Value("${faceServer.url}")
private String serverUrl;
// ...
}
You can either have default values for the #Value annotations or use some default property values in application.yml.
Now just override those properties in your test with the values you want:
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = TestConfig.class)
#TestPropertySource(properties = {
"faceServer.version=1.0",
"faceServer.url=http://127.0.0.1:8066/api/ZKComparison"
})
public class FaceServerTestByTyler {
#Autowired
private FaceComparisonServer faceComparisonServer;
// ...
}
However...
The second option is to make your classes more unit-testable. Prefer construction injection over field injection, and you can test your classes more independently.
#Service
public class FaceComparisonServerImpl implements FaceComparisonServer {
private final FaceServer faceServer;
public FaceComparisonServerImpl(FaceServer faceServer) {
this.faceServer = faceServer;
}
#Override
public FaceComparsionInfo getServerInfo() {
String serverInfo = faceServer.getServerInfo();
// ...
}
}
This now becomes unit-testable:
public class FaceServerTestByTyler {
private FaceComparisonServer faceComparisonServer;
private FaceServer faceServer;
#BeforeEach
public setup() {
faceServer = mock(FaceServer.class);
faceComparisonServer = new FaceComparisonServer(faceServer);
}
#Test
public void getServerInfo() {
when(faceServer.getServerInfo()).thenReturn(xxx);
// ...
}
}
The second option ends up with a test that runs much faster than any solutions that suggest to create a mock bean through a test configuration.
I want to create Clazz, where I can create two Beans with the same class, but with the different configuration.
public class Clazz {
//same class : Client, inside has the different configuration
//inicilized by methods
#Bean(name="Bean1")
public Client1 (){}
#Bean(name = "Bean2")
public Clien2t (){}
}
Then I want to inject them in other classes
public class ClassForInjectBean1{
#Autowired
#Qualifier("Bean1")
#NotNull
Client client
....
}
public class ClassForInjectBean2{
#Autowired
#Qualifier("Bean2")
#NotNull
Client client
....
}
I have tried this construction in classes ClassForInjectBean1 and ClassForInjectBean2
#Resource(name = "Bean2")
#NotNull
Client client
and
#Autowired
#Qualifier("Bean2")
But spring does not understand
Ошибка :
Parameter 1 of constructor in ClassForInjectBean1 required a single bean, but 2 were found:
- Bean1: defined by method 'Client1' in class path resource...
- Bean2: defined by method ''Client2' in class path resource...
Why I can't do that?
I know that there is this way https://www.baeldung.com/spring-qualifier-annotation, but I don't to create many classes and interfaces.
Try to use #Configuration.
Indicates that a class declares one or more #Bean methods and may be
processed by the Spring container to generate bean definitions and
service requests for those beans at runtime
I provided some example for you.
#Configuration
public class Cfg {
#Bean("client1")
public Client client1() {
return new Client("client1");
}
#Bean("client2")
public Client client2() {
return new Client("client2");
}
}
public class Client {
private String name;
public Client(String name) {
this.name = name;
}
#Override
public String toString() {
return "Client{" +
"name='" + name + '\'' +
'}';
}
}
#Component
public class InjectionTest {
#Component
public class ClassForInjectBean1 {
private final Client client;
public ClassForInjectBean1(#Qualifier("client1") Client client) {
this.client = client;
}
#PostConstruct
public void testInit() {
System.out.println(client.toString());
}
}
#Component
public class ClassForInjectBean2 {
private final Client client;
public ClassForInjectBean2(#Qualifier("client2") Client client) {
this.client = client;
}
#PostConstruct
public void testInit() {
System.out.println(client.toString());
}
}
}
Output would be:
Client{name='client2'}
Client{name='client1'}
I have a consumer.properties file with the following contents in src/main/resources, and an accompanying Configuration class that loads and stores the file's contents into class member variables:
//consumer.properties file in src/main/resources:
com.training.consumer.hostname=myhost
com.training.consumer.username=myusername
com.training.consumer.password=mypassword
//ConsumerConfig.java
#Configuration
#PropertySource(
value= {"classpath:consumer.properties"}
)
#ConfigurationProperties(prefix="com.training.consumer")
public class ConsumerConfig {
private String hostname;
private String username;
private String password;
public ConsumerConfig() { }
public String getHostname() {
return hostname;
}
public void setHostname(String hostname) {
this.hostname = hostname;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
#Override
public String toString() {
return "ConsumerConfig [hostname=" + hostname + ", username=" + username + ", password=" + password + "]";
}
}
I also have a ConfigsService class that autowires the ConsumerConfig class to retrieve the individual properties:
#Component
public class ConfigsService {
#Autowired
ConsumerConfig consumerConfig;
public ConsumerConfig getConsumerConfig() {
return consumerConfig;
}
public void showConfig() {
consumerConfig.toString();
}
public ConsumerConfig getConfig() {
return consumerConfig;
}
}
The properties are loaded up just fine when running the ConfigsService's methods. The problem is in the unit tests, where invoking configService.getConfig().getHostname() returns a null value -- even after having created a src/test/resources directory, and adding my consumer.properties file in it:
#TestPropertySource("classpath:consumer.properties")
public class ConfigsServiceTest {
#Mock
ConsumerConfig consumerConfig;
#InjectMocks
ConfigsService configService;
#Before
public void beforeEach() {
MockitoAnnotations.initMocks(this);
}
#Test
public void someTest() {
System.out.println(configService.getConfig().getHostname()); //outputs null here -- wth!
Assert.assertTrue(true);
}
}
you are getting null values because of the mock object. I will suggest using spring runner with context configuration which will load properties in the config class and create the bean.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {ConsumerConfig.class, ConfigsService.class})
#TestPropertySource(locations = "classpath:consumer.properties")
public class ConfigsServiceTest {
#Autowired
private ConfigsService configsService;
#Test
public void someTest() {
Assert.assertNotNull(configService.getConfig().getHostname());
}
}
The error may be because in the ConfigsServiceTest class you are establishing that your ConsumerConfig is a Mock and that is why it is not loading your configuration if you remove that code it should work
I'm developing a web application with spring. I've had no problem autowiring and using database #Service classes. Now I'm trying to read a global property file and provide the values to all classes that need them. The solution I've come up with so far seem to be overly complicated (too many classes - AppConfig, ServerConfig iface, ElasticServerConfig) for such a trivial task but I could live with it if it worked.
my applicationContext.xml contains
<context:component-scan base-package="my.package" />
AppConfig.java:
package my.package.configuration;
#Configuration
#PropertySource("classpath:application.properties")
public class AppConfig {
}
ServerConfig.java:
public interface ServerConfig {
String getUrl();
String getUser();
String getPassword();
}
ElasticSearchConfig.java:
package my.package.configuration;
#Component(value = "elasticServerConfig")
public class ElasticServerConfig implements ServerConfig {
private static final Logger LOGGER = LogManager.getLogger(ElasticServerConfig.class);
private String url;
private String user;
private String password;
#Autowired
public ElasticServerConfig(final Environment env) {
this.url = env.getProperty("elastic_server.url");
this.user = env.getProperty("elastic_server.user");
this.password = env.getProperty("elastic_server.password");
LOGGER.debug("url=" + url + "; user=" + user + "; password=" + password); // this works!
}
#Override
public final String getUrl() {
return url;
}
#Override
public final String getUser() {
return user;
}
#Override
public final String getPassword() {
return password;
}
}
When the web application boots, the ElasticServerConfig constructor prints out the correct url/user/pwd as read from application.properties. However an instance of ElasticServerConfig is not injected into a Search object:
package my.package.util;
public class Search {
#Autowired
#Qualifier("elasticServerConfig")
private ServerConfig elasticServerConfig;
public final List<Foobar> findByPatternAndLocation() {
if (elasticServerConfig == null) {
LOGGER.error("elasticServerConfig is null!");
}
// and i get a NullPointerException further on
// snip
}
}
You have to register the Search class as a Spring Bean and take it from the Spring context when you want to use it. It's important to get the bean from the spring context. If you create an object of that class with new, Spring has no way to know about that class and mange it's dependencies.
You can get get a bean from the Spring context by #Autowire it somewhere or by accessing an instance of the context and use the getBean method:
#Configuration
#PropertySource("classpath:application.properties")
public class AppConfig {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(AppConfig.class, args);
ctx.getBean...
}
}
Either use #Component annotation on the class and make sure that the class is in package thats under my.package
or register it in the configuration class
#Configuration
#PropertySource("classpath:application.properties")
public class AppConfig {
#Bean
public Search search(){
return new Search();
}
}