Reputation: 63830
I'm trying to write tests for a class that has a field value injected from a properties file. I'm trying to utilize the TestPropertySource
annotation to get values in there while running TestNG tests, but it seems to be ignoring my properties file.
There's dozens of similar questions, which I tried to carefully read and try their implementations where possible. It seems my question is yet slightly different though, here's why:
"@TestPropertSource and @PropertySource don't work for JUnit": talks about JUnit instead of TestNG (might or might not be relevant?) and it also talks about injecting a property value into a field on the test class (as opposed to the unit under test).
"@TestPropertySource not working?": talks about how to ignore missing properties, not how to fix it.
"@TestPropertySource is not loading properties": also talks about injecting values in to test class fields (did try @RunWith
or @SpringBootTest
but that didn't help either).
The other links in Google search results also didn't help.
What do you need to do to get a unit-under-test with @Value
annotated fields set with their property? Can I somehow request spring to provide me with instances of my class instead of new
ing them myself?
Here's a minimal repro.
Foo.java
package nl.jeroenheijmans.stackoverflow.testngprops;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Foo {
@Value("${my.prop}")
private String myProp;
public String ExposeProp() {
return myProp;
}
}
FooTest.java
package nl.jeroenheijmans.stackoverflow.testngprops;
import org.springframework.test.context.TestPropertySource;
import org.testng.Assert;
import org.testng.annotations.Test;
@TestPropertySource("classpath:application.properties")
public class FooTest {
@Test
public void sanityCheck(){
Foo foo = new Foo();
Assert.assertNotNull(foo); // Success!
}
@Test
public void testProperty() {
Foo foo = new Foo();
Assert.assertEquals(foo.ExposeProp(), "checkcheck"); // Fail!
}
}
application.properties (in both the main
and test
folder)
my.prop=checkcheck
Main.java
package nl.jeroenheijmans.stackoverflow.testngprops;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;
import org.springframework.context.annotation.PropertySource;
@SpringBootApplication
@PropertySource(value = {"classpath:application.properties"})
public class Main extends SpringBootServletInitializer {
public static void main(String... args) {
SpringApplication.run(Main.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Main.class);
}
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>nl.jeroenheijmans.stackoverflow</groupId>
<artifactId>testngprops</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${org.springframework.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>${testng.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven.compiler.version}</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</build>
<properties>
<java.version>1.8</java.version>
<maven.compiler.version>3.5</maven.compiler.version>
<org.springframework.boot.version>1.5.1.RELEASE</org.springframework.boot.version>
<testng.version>6.9.10</testng.version>
<mockito.version>1.9.5</mockito.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
Upvotes: 3
Views: 2835
Reputation: 14746
Here's how I solved this.
In a nutshell, I believe you were missing the part of extending org.springframework.test.context.testng.AbstractTestNGSpringContextTests
and using Dependency Injection for the object Foo
via the @Autowire
annotation. Since you were instantiating the Foo
object, the values weren't being injected into it, which explains why the assertions were failing.
Main.java
package com.rationaleemotions.stackoverflow.qn45716815;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;
import org.springframework.context.annotation.PropertySource;
@SpringBootApplication(exclude = {MongoAutoConfiguration.class, MongoDataAutoConfiguration.class})
@PropertySource(value = {"classpath:application.properties"})
public class Main extends SpringBootServletInitializer {
public static void main(String... args) {
SpringApplication.run(Main.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Main.class);
}
}
The reason for using @SpringBootApplication(exclude = {MongoAutoConfiguration.class, MongoDataAutoConfiguration.class})
can be found in this thread : Mongo tries to connect automatically to port 27017(localhost)
Update: Exclusion of Mongo configuration is optional and you don't need to be doing this, if you have a project which is properly setup for Mongo.
Here's how the FooTest.java looks like
package com.rationaleemotions.stackoverflow.qn45716815;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.Assert;
import org.testng.annotations.Test;
@TestPropertySource("classpath:application.properties")
@SpringBootTest
public class FooTest extends AbstractTestNGSpringContextTests{
@Autowired
private Foo foo;
@Test
public void sanityCheck() {
Assert.assertNotNull(foo);
}
@Test
public void testProperty() {
Assert.assertEquals(foo.ExposeProp(), "checkcheck");
}
}
Here's how my maven dependencies looks like
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>1.5.6.RELEASE</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>1.5.6.RELEASE</version>
<scope>test</scope>
</dependency>
The reason for adding an exclusion to spring-boot-starter-logging can be found here : Disable Logback in SpringBoot
Update: Exclusion of logback is optional and you don't need to be doing this, if you have a project which is properly setup for working with logback.
Here's the output when I ran this test :
objc[36167]: Class JavaLaunchHelper is implemented in both /Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/bin/java (0x1026784c0) and /Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/libinstrument.dylib (0x1027404e0). One of the two will be used. Which one is undefined.
log4j:WARN No appenders could be found for logger (org.springframework.test.context.BootstrapUtils).
log4j:WARN Please initialize the log4j system properly.
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.5.6.RELEASE)
===============================================
Default Suite
Total tests run: 2, Failures: 0, Skips: 0
===============================================
Upvotes: 3