简体   繁体   中英

Spring Boot Unit Testing

I'm currently developing a Rest service in Spring Boot and am attempting to develop unit tests. My Rest controller takes the request and utilizes an Sftp Connector defined within another package to directly interface with our servers. I've defined my test cases to mock the behavior of the Sftp Connector rather than have it execute with the mock data being fed into the Rest endpoints. I've been getting a ton of error logs during testing showing that exceptions are being raised from the Sftp connector. Which makes sense if the mock data sent to the endpoints is still being fed into the connector class. I'm guessing that I'm not mocking the connector properly. I've been unsuccessful so far in locating a method to mock the connector. Is this possible? Or can I only mock the classes defined within the same package as the test class?

Controller:

package RestAPI;

import JSch.SFTP.SftpConnector;
import JSch.SFTP.fileInfo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.util.Collection;
import java.util.Iterator;
import java.util.Properties;


@RestController
@Api( value = "Test Rest Controller", description = "Basic test for SFTP 
connector with very basic operations.")
public class SftpRestController {

    private Properties getProperties(){ //used to get connection values from 
 external file
    InputStream input = null;
    Properties prop = new Properties(); //new property
    try{
        if(System.getProperty("os.name").contains("Windows")){
            input = new FileInputStream("C:\\tmp\\application.properties"); //get resources stream
        }
        else{
            input = new FileInputStream("application.properties");
        }
        BufferedReader reader = new BufferedReader(new InputStreamReader(input));
        String line;
        while((line = reader.readLine()) != null){
            String[] split = line.split("=");
            prop.put(split[0], split[1]);
        }

    }catch(IOException IO){
        IO.printStackTrace();
    }finally{
        if(input != null )
            try{
                input.close(); //close the stream
            }catch(IOException IO2){
                IO2.printStackTrace();
            }
    }
    System.out.println(prop.getProperty("sftp.port"));
    return prop;
}

@ApiOperation(value = "Check that service is up and running.", response = String.class)
@RequestMapping("/")
public @ResponseBody String index(){ return "Rest Test Service is up and running";}


@ApiOperation(value = "Upload a file", response = String.class)
@PostMapping(value="/Upload") //uploads files so long as the files are defined in one structure in the form body
public @ResponseBody String multipartUpload(@RequestBody MultipartFile[] files, @RequestParam("dest") String dest){
    String fileMessage = ""; //need something to send back to the caller
    SftpConnector connector = new SftpConnector(this.getProperties()); //plugs properties into the constructor and opens a connection
    for( int i = 0; i < files.length; i++ ){ //for each file uploaded
        connector.uploadFile(files[i], dest); //call the upload method with the file reference and where it's going
        fileMessage = fileMessage + files[i].getOriginalFilename() + " has been uploaded to remote directory \n";
    }
    connector.closeSFTPConnection(); //manually close the connection
    return fileMessage;
}

@ApiOperation(value = "Downloads a file over SFTP and writes it to local file system specified", response = String.class)
@GetMapping(value="/Download")
public @ResponseBody String downloadFile(@RequestParam("fileName") String fName, @RequestParam("dest") String dest){
    SftpConnector connector = new SftpConnector(this.getProperties()); //new connector
    connector.downloadFile(fName, dest); //pull the specified file down
    connector.closeSFTPConnection(); //close the connection
    return fName + " has been downloaded to the specified local directory";
}

@ApiOperation(value = "Moves all files on a remote server from one directory to another based on file type specified", response = String.class)
@PutMapping(value="/moveFiles")
public @ResponseBody String moveFiles(@RequestParam("dir") String origin, @RequestParam("fileType") String type,@RequestParam("dest") String dest){
    SftpConnector connector = new SftpConnector(this.getProperties());
    connector.moveFiles(origin, type, dest); //moves a group of files based file type from one directory to another
    connector.closeSFTPConnection();
    return "All files have been moved.";
}

@ApiOperation(value = "Moves a single specific file", response = String.class)
@GetMapping(value="/moveFile")
public @ResponseBody String moveFile(@RequestParam("dir") String origFile, @RequestParam("dest") String dest){
    SftpConnector connector = new SftpConnector(this.getProperties());
    connector.moveFile(origFile, dest); //moves a single file.  file name must be specified.
    connector.closeSFTPConnection();
    return FilenameUtils.getName(origFile) + " has been moved to " + dest;
}

@ApiOperation(value = "Gets a specified file stream and returns it as a string", response = String.class)
@GetMapping(value="/readFile")
public @ResponseBody String readFile(@RequestParam("path") String path){
    String fileContents;
    try{
        SftpConnector connector = new SftpConnector(this.getProperties());
        InputStream fis = connector.readFile(path); //gets an open file stream
        fileContents = IOUtils.toString(fis,"UTF-8"); //takes a file stream, reads into variable in specified encoding
        fis.close(); //closes the stream
        connector.closeSFTPConnection();
    }catch(IOException IO){
        fileContents = IO.toString();
    }
    return fileContents;
}

@ApiOperation(value="Returns a list of file names as a string", response = String.class)
@GetMapping(value="/listFiles")
public @ResponseBody String fileNames(@RequestParam("dir") String origin, @RequestParam("fileType") String type){
    String base = "";
    try{
        SftpConnector connector = new SftpConnector(this.getProperties());
        Collection<fileInfo> files = connector.listFiles(origin, type); //gets a list of files with name, type, and an input stream
        Iterator<fileInfo> iterator = files.iterator(); //iterator to roll over collection
        while(iterator.hasNext()){ //while not empty
            fileInfo thisFile = iterator.next(); //get the next fileInfo
            base = base + thisFile.getName() + '\n' ; //write the name to the base string
            thisFile.getFileStream().close(); //close the file stream
        }
        connector.closeSFTPConnection();
    }catch(Exception ex){
        ex.printStackTrace();
    }
    return base;
}

@ApiOperation(value = "Moves a file by the connectors readFile and writeFile methods rather than SFTP rename", response = String.class)
@GetMapping(value="/test")
public @ResponseBody String StreamTest(@RequestParam("file") String file, @RequestParam("dest") String dest){
    SftpConnector connector = new SftpConnector(this.getProperties());
    connector.writeFile(dest, FilenameUtils.getName(file), connector.readFile(file));
    connector.closeSFTPConnection();
    return file + " has been successfully read, and written to destination";
}
}

Sftp Connector:

package JSch.SFTP;

import com.jcraft.jsch.*;
import org.apache.commons.io.FilenameUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.util.*;

@Component
public class SftpConnector {
private String sftpHost;
private String sftpUser;
private int sftpPort;
private String sftpPass;
private Session session = null;
private ChannelSftp channelSftp = null;

public String getSftpHost() {
    return sftpHost;
}

public void setSftpHost(String sftpHost) {
    this.sftpHost = sftpHost;
}

public String getSftpUser() {
    return sftpUser;
}

public void setSftpUser(String sftpUser) {
    this.sftpUser = sftpUser;
}

public int getSftpPort() {
    return sftpPort;
}

public void setSftpPort(int sftpPort) {
    this.sftpPort = sftpPort;
}

public String getSftpPass() {
    return sftpPass;
}

public void setSftpPass(String sftpPass) {
    this.sftpPass = sftpPass;
}

public void setConnectionVars(String host, String user, String password, int port){
    this.setSftpHost(host);
    this.setSftpPort(port);
    this.setSftpUser(user);
    this.setSftpPass(password);
}

public SftpConnector(Properties prop){
    this.setConnectionVars(prop.getProperty("sftp.host"), prop.getProperty("sftp.user"), prop.getProperty("sftp.pass"), Integer.parseInt(prop.getProperty("sftp.port"))); //set the connection variables
    this.openSFTPConnection(); //open the sftp connection
}

public Session getSession() {
    return session;
}

public void setSession(Session session) {
    this.session = session;
}

public ChannelSftp getChannelSftp() {
    return channelSftp;
}

public void setChannelSftp(ChannelSftp channelSftp) {
    this.channelSftp = channelSftp;
}

public void openSFTPConnection(){
    try{
        JSch jsch = new JSch(); //create the new JSch var
        Session baseSession = jsch.getSession(this.getSftpUser(), this.getSftpHost(),this.getSftpPort()); //establish the ssh session info
        baseSession.setPassword(this.getSftpPass()); //auth over password
        baseSession.setConfig("StrictHostKeyChecking","no"); // don't require hosting key check will need to change after testing
        baseSession.setConfig("PreferredAuthentications","password"); //used to omit kerberos authentication
        this.setSession(baseSession); //set the session to private variable
        this.getSession().connect(); //open the session
        ChannelSftp tempChannel = (ChannelSftp) this.getSession().openChannel("sftp"); //create an SFTP channel
        this.setChannelSftp(tempChannel); //set the channel to the private variable
        this.getChannelSftp().connect(); //open the SFTP connection
    }catch(JSchException e){
        e.printStackTrace();
    }
}

public void closeSFTPConnection(){
    try{
        if(this.getChannelSftp().isConnected()){ //if the channel is open so must be the session close both
            this.getChannelSftp().disconnect();
            this.getSession().disconnect();
        }else{ //if the channel is closed check to see if the session is still open
            if(this.getSession().isConnected()){
                this.getSession().disconnect();
            }
        }
    }catch(Exception e){
        e.printStackTrace();
    }
}

public Collection<fileInfo> listFiles(String dir, String filter){
    Collection<fileInfo> files = new ArrayList<fileInfo>();
    try{
        if(this.getChannelSftp() == null || !this.getChannelSftp().isConnected())     //make sure that the JSch sessions been opened.
            this.openSFTPConnection();
        this.getChannelSftp().cd(dir); //set the working directory
        Vector<ChannelSftp.LsEntry> fileList = this.getChannelSftp().ls(filter); //Get a listing of the files in the directory depending on a filter
        Iterator<ChannelSftp.LsEntry> iterator = fileList.iterator(); //iterate over the collection
        while(iterator.hasNext()) {
            ChannelSftp.LsEntry entry = iterator.next();
            fileInfo thisFile = new fileInfo(entry.getFilename(), entry.getAttrs().getAtimeString(), FilenameUtils.getExtension(entry.getFilename()), this.getChannelSftp().get(entry.getFilename())); //get a buffered input stream for each file.
            files.add(thisFile);
        }
    }catch(SftpException e){
        e.printStackTrace();
    }
    return files;
}

public InputStream readFile(String filePath){ //built to read get a file stream from a given file path
    InputStream inputStream = null;
    try{
        if(this.getChannelSftp() == null || !this.getChannelSftp().isConnected()) //if the channel isn't open, open it.
            this.openSFTPConnection(); //open connection
        inputStream = this.getChannelSftp().get(filePath); //get the stream

    }catch(SftpException ex){
        ex.printStackTrace();
    }
    return inputStream;
}

public String uploadFile(MultipartFile file, String dest){
    try{
        System.out.println(this.getSession().getConfig("FileUploadBaseSizeLimit"));
        if(this.getChannelSftp() == null || !this.getChannelSftp().isConnected()) //if not connected open the connection
            this.openSFTPConnection();
        this.getChannelSftp().cd(dest); //set working dir
        this.getChannelSftp().put(file.getInputStream(), dest + '/' + file.getOriginalFilename()); //get the input stream from the multipart file and put it in the destination
    }catch(IOException IO){
        IO.printStackTrace();
    }catch(SftpException sftp){
        sftp.printStackTrace();
    }
    return file.getOriginalFilename() + " has been uploaded";
}

public String downloadFile(String orig, String dest){
    try{
        if(this.getChannelSftp() == null || !this.getChannelSftp().isConnected()) //if not connected
            this.openSFTPConnection();
        File download = new File(dest + '/' + FilenameUtils.getName(orig)); //create a new local file
        OutputStream outputStream = new FileOutputStream(download);  //generate an output stream to the file
        byte[] buffer = new byte[10000000]; //10MB buffer instance
        BufferedInputStream bufferedInputStream = new BufferedInputStream(this.getChannelSftp().get(orig)); //create a buffered input stream off the input stream from the ssh get
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream); // and a buffered output stream off the output stream we created
        int readCount; //integer link to the buffer
        while((readCount = bufferedInputStream.read(buffer)) > 0){ //read the buffered input stream into the buffer.  while it's not empty...
            bufferedOutputStream.write(buffer, 0, readCount); //write the buffer to the output stream
        }
        bufferedInputStream.close(); //close the streams
        bufferedOutputStream.close();
        outputStream.close();

    }catch(IOException IO){
        IO.printStackTrace();
    }catch(SftpException sftp){
        sftp.printStackTrace();
    }
    return "File " + FilenameUtils.getName(orig)  + " has been downloaded."; //return that we've successfully uploaded the file
}

public void writeFile(String dir, String fileName, InputStream fileIS) { //writes a file given the destination directory, fileName, and an input stream
    try{
        if(this.getChannelSftp() == null || !this.getChannelSftp().isConnected() ) //if the connection isn't already open, open it
            this.openSFTPConnection();
        this.getChannelSftp().cd(dir); //set the working dir for better shorthand
        this.getChannelSftp().put(fileIS, fileName); //put the file.
        fileIS.close(); //close the file stream

    }catch(SftpException e){
        e.printStackTrace();
    }catch(IOException IO){
        IO.printStackTrace();
    }

}

public void moveFile(String path, String dest){
    try{
        if(this.getChannelSftp() == null || !this.getChannelSftp().isConnected()){ //open connection if not done already
            this.openSFTPConnection();
        }
        this.getChannelSftp().rename(path, dest + '/' + FilenameUtils.getName(path)); //ssh command to move the file.
    }catch(SftpException e){
        e.printStackTrace();
    }
}

public String moveFiles(String dir, String filter, String dest){
    try{
        if(this.getChannelSftp() == null || !this.getChannelSftp().isConnected()){ //open if not done already
            this.openSFTPConnection();
        }
        Collection<fileInfo> files = this.listFiles(dir, filter); //invoke the listFiles method and convert to an array
        Iterator<fileInfo> iterator = files.iterator(); //iterator
        while( iterator.hasNext()){ //while there is another value in the collection
            fileInfo thisFile = iterator.next(); // have to store the fileInfo somewhere.  can't just keep calling next.
            this.getChannelSftp().rename(dir + '/' + thisFile.getName(), dest + '/' + thisFile.getName());
        }
        this.closeSFTPConnection(); //close the connection
    }catch(Exception ex){
        ex.printStackTrace();
    }
    return "all files moved";
}
}

Test Class:

package RestAPI;

import JSch.SFTP.SftpConnector;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import java.io.FileInputStream;
import java.io.InputStream;


@RunWith(SpringRunner.class)
@WebMvcTest(value = SftpRestController.class, secure = false)
public class SftpRestUnitTest {

@Autowired
private MockMvc mockMvc;

@MockBean
SftpConnector connector ;

@Test
public void testFileUpload() throws Exception{
    MockMultipartFile testFile1 = new MockMultipartFile("data", "WSUID1.csv", "application/csv", "some xml".getBytes());
    Mockito.when(
            connector.uploadFile(Mockito.any(),Mockito.anyString())
    ).thenReturn("WSUID1.csv has been uploaded");
    MvcResult result = mockMvc.perform(MockMvcRequestBuilders.multipart("/Upload").file(testFile1).param("dest", "/tmp/test")).andReturn();
    System.out.println(result.getResponse().getContentAsString());
    assert result.getResponse().getContentAsString().equals("WSUID1.csv has been uploaded to remote directory \n");
}
@Test
public void testDownload() throws Exception{
    String expected = "TestFile.csv has been downloaded to the specified local directory";
    Mockito.when(
            connector.downloadFile(Mockito.anyString(), Mockito.anyString())
    ).thenReturn("File TestFile.csv has been downloaded.");
    RequestBuilder request = MockMvcRequestBuilders.get("/Download").param("fileName","/tmp/TestFile.csv").param("dest", "/tmp");
    MvcResult result = mockMvc.perform(request).andReturn();
    System.out.println(result.getResponse().getContentAsString());
    assert result.getResponse().getContentAsString().equals(expected);
}
@Test
public void testMoveFiles() throws Exception{
    Mockito.when(
            connector.moveFiles(Mockito.anyString(),Mockito.anyString(),Mockito.anyString())
    ).thenReturn("all files moved");
    RequestBuilder request = MockMvcRequestBuilders.put("/moveFiles").param("dir","/IB_Test").param("fileType", "*.csv").param("dest", "/InternatlPgms/INTO/Archive/Receipt");
    MvcResult result = mockMvc.perform(request).andReturn();
    System.out.println(result.getResponse().getContentAsString());
    assert result.getResponse().getStatus() == 200;
}
@Test
public void testReadFile() throws Exception{
    String expected = "greetings from java test";
    InputStream mockStream = Mockito.mock(FileInputStream.class);
    Mockito.when(
            connector.readFile(Mockito.anyString())
    ).thenReturn(mockStream);
    RequestBuilder request = MockMvcRequestBuilders.get("/readFile").param("path", "IB_Test");
    MvcResult result = mockMvc.perform(request).andReturn();
    System.out.println(result.getResponse().getContentAsString());
    assert result.equals(expected);
}
}

Error Logs:

The specified file is a directory.
at com.jcraft.jsch.ChannelSftp.throwStatusError(ChannelSftp.java:2873)
at com.jcraft.jsch.ChannelSftp.get(ChannelSftp.java:1337)
at com.jcraft.jsch.ChannelSftp.get(ChannelSftp.java:1290)
at JSch.SFTP.SftpConnector.readFile(SftpConnector.java:133)
at RestAPI.SftpRestController.readFile(SftpRestController.java:104)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:189)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:800)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1038)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:897)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:634)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882)
at org.springframework.test.web.servlet.TestDispatcherServlet.service(TestDispatcherServlet.java:71)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
at org.springframework.mock.web.MockFilterChain$ServletFilterProxy.doFilter(MockFilterChain.java:166)
at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:133)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:133)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:92)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:133)
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)

Any help is greatly appreciated.

You create a new instance of SftpConnector in every method of your controller. It is not an injected bean so it won't be replaced with your mock.

For an example have a look at this article (section 5): https://www.baeldung.com/spring-boot-testing

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM