Jeroen
Jeroen

Reputation: 63830

TestNG test not using TestPropertySource for injecting @Value

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:

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 newing 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

Answers (1)

Krishnan Mahadevan
Krishnan Mahadevan

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

Related Questions