Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

FileLoader

LiveFileLoaderTest - not suitable for a build server and breaks the tenets of a good unit test

Code Block
package com.celestial.mockito.filetodb;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Filesstatic org.junit.jupiter.api.Assertions.assertEquals;
import javaorg.niojunit.file.Paths;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import static org.junit.Assert.*;jupiter.api.Test;


/**
 *
 * @author selvy
 */
public class LiveFileLoaderTest 
{
    // The inital design is described in this test
    /*
     The weakness should be obvious?  The file to be loaded and it's location
     I use a shared network drive to run this code from different machines, normally
     dev-ing from a PC.  When I ran the code on the laptop from a cafe it 
     immediately failed because the C: on the laptop was completely different
     to the PC, so the original file C:/tmp/KeyboardHandler.txt did not exist
    
     THIS IS A GREAT EXAMPLE OF WHY THE UNIT TEST AND CUT SHOULD NOT BE STRONGLY
     LINKED TO ANY IO - NETWORK, DB, AND FILE SYSTEM
    */
     @Test
     public void load_all_of_file_using_inbuilt_Files_type() 
     {
         // arrange
         String fileToLoad = "c:/tmp/kipstor-settings.js";
         FileLoader cut = new FileLoader(fileToLoad);
         int expectedBytesRead = 301;
         
         // act
         int bytesRead = cut.loadFile(fileToLoad);
         
         // assert
         assertEquals(expectedBytesRead, bytesRead);
     }
}

...

Code Block
package com.celestial.mockito.filetodb;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;


/**
 *
 * @author selvy
 */
public class FileLoader 
{
    private  class IntWrapper
    {
        public   int value;
    }
    String fileToLoad;
    List<String> lines = Collections.emptyList();

    public FileLoader(String fileToLoad) 
    {
        this.fileToLoad = fileToLoad;
    }

    int loadFile(String fname) 
    {
        try
        {
            lines = Files.readAllLines(Paths.get(fname), StandardCharsets.UTF_8);
        }
        catch (IOException e){}

        return calculateFileSize();
    }    
    
    private int calculateFileSize()
    {
        IntWrapper result = new IntWrapper();
        
        lines.forEach(line -> {
                result.value += line.length();
        });
        
        return result.value;
    }
}

...

FileLoaderTest - suitable for a build pipeline and does not break the tenets of unit testing

Code Block
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.celestial.mockito.filetodb;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.junit.Test;
import static org.junit.Assert.*;
import org.mockito.MockedStatic;
import org.mockito.Mockito;

/**
 *
 * @author selvy
 */
public class FileLoaderTest 
{
package com.celestial.mockito.filetodb;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.junit.Test;
import static org.junit.Assert.*;
import org.mockito.MockedStatic;
import org.mockito.Mockito;

/**
 *
 * @author selvy
 */
public class FileLoaderTest 
{
    // To use a different type of file system loader, pass a lambda to loadFile()
    // as shown here
    /*
        int bytesRead = cut.loadFile((fname) ->
        {
           List<String> result = null;
           try
           {
               result = Files.readAllLines(Paths.get(fname), StandardCharsets.UTF_8);
           }
           catch (IOException e){}
           return result;
        });
    */

     // Redesign the FileLoader so that the machenism to load files up can be 
     // passed in as a lambda - still titghtly coupled the file system
     @Test
     public void load_all_of_file_using_inbuilt_Files_type_as_lambda() 
     {
         // arrange
         String fileToLoad = "c:/tmp/KeyboardHandler.txt";
         FileLoader cut = new FileLoader(fileToLoad);
         int expectedBytesRead = 10;    //1371;
         List<String> pretendFileContent = new ArrayList<>();
         pretendFileContent.add("Hello");
         pretendFileContent.add("world");
         MockedStatic<Files> ff = Mockito.mockStatic(Files.class);
         ff.when(() -> Files.readAllLines(Paths.get(fileToLoad), StandardCharsets.UTF_8)).thenReturn(pretendFileContent);
         
         // act
         int bytesRead = cut.loadFile((fname) ->
         {
            List<String> result = null;
            try
            {
                result = Files.readAllLines(Paths.get(fname), StandardCharsets.UTF_8);
            }
            catch (IOException e){}
            return result;
         });
         
         // assert
         assertEquals(expectedBytesRead, bytesRead);
     }

     // Redesign has worked so this test uses a stub to simulate a file being
     // loaded, it is passed in as lambda - We've decoupled ourselves from the 
     // filesystem
     @Test
     public void load_all_of_file_via_stub() 
     {
         // arrange
         String fileToLoad = "c:/tmp/KeyboardHandler.txt";
         FileLoader cut = new FileLoader(fileToLoad);
         int expectedBytesRead = 10;
         
         // act
         int bytesRead = cut.loadFile((fname) ->
         {
            List<String> result = new ArrayList<>();
            
            result.add("Hello");
            result.add("world");

            return result;
         });
         
         // assert
         assertEquals(expectedBytesRead, bytesRead);
     }
     
     
}

FileLoader implementation code - refactored

Code Block
languagejava
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.celestial.mockito.filetodb;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;


/**
 *
 * @author selvy
 */
public class FileLoader 
{
    class   IntWrapper
    {
        int value;
    }
    
    String fileToLoad;
    List<String> lines = Collections.emptyList();

    public FileLoader(String fileToLoad) 
    {
        this.fileToLoad = fileToLoad;
    }

    public List<String> getLines() {
        return lines;
    }
    
    int loadFile(String fname) 
    {
        try
        {
            lines = Files.readAllLines(Paths.get(fname), StandardCharsets.UTF_8);
        }
        catch (IOException e)
        {
        }

        return calculateFileSize();
    }    
    
    int loadFile(ILoader func) 
    {
        lines = func.loadFile(fileToLoad);
        return calculateFileSize();
    }    
    
    private int calculateFileSize()
    {
        IntWrapper result = new IntWrapper();
        
        lines.forEach(line -> {
                result.value += line.length();
        });
        
        return result.value;
    }
}

DBConnector

LiveDBConnectorTest - not suitable for a build server and breaks the tenets of a good unit test

Code Block
languagejava
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.celestial.mockito.filetodb;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import static org.junit.Assert.*;
import org.junit.Test;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.mockito.MockedStatic;
import org.mockito.Mockito;

/**
 *
 * @author selvy
 */
public class LiveDBConnectorTest
{
    static String dbEndPoint = "jdbc:mysql://localhost:3306/";
    static String dbName = "files";
    
    
    // This test is not suitable for a build server. so we will need to stub
    // IDbConnectionMgr
     //@Ignore
     @Test
     public void open_connection() throws SQLException 
     {
        // arrange
        String dbEndPoint_ = "jdbc:mysql://localhost:3306/";
        String dbName_ = "files";
        IDbConnectionMgr connection = (endPoint, db) -> {
            Connection conn = DriverManager.getConnection(endPoint+db, "root", "ppp");
            return conn;
        };
        DBConnector cut = new DBConnector(connection);

        // act
        Connection conn = cut.openConnection(dbEndPoint_, dbName_);
         
         // assert
        assertNotNull(conn);
     }
     
     @Test
     public void write_line_to_live_connection() throws SQLException 
     {
         // arrange
         IDbConnectionMgr connection = (endPoint, db) -> {
             Connection conn = DriverManager.getConnection(endPoint+db, "root", "ppp");
             return conn;
         };
         DBConnector cut = new DBConnector(connection);
         cut.openConnection(dbEndPoint, dbName);
         String lineToWrite = "be not changed by...";
         int expectedLineNo = 1;
         
         // act
         // The idea of writeLine() is that each time a line is written to the DB
         // it's line No should be returned.  Line Nos start at 1
         int lineNo = cut.writeLine(lineToWrite);
         
         // assert
         assertEquals(expectedLineNo, lineNo);
     }
}

DBConnector - Initial implementation

Code Block
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.celestial.mockito.filetodb;

import com.mysql.cj.xdevapi.PreparableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author selvy
 */
public class DBConnector
{
    private IDbConnectionMgr connectionMgr;
    private Connection theConnection;
    private int fileId;
    private static final String SQL_INSERT_LINE = "insert into files.line_in_file" +
         " values (default, ?, ?);";
    private static final String SQL_LINE_COUNT = "select count(*) from files.line_in_file;";
    
    DBConnector(IDbConnectionMgr connection)
    {
        connectionMgr = connection;
    }

    Connection  openConnection(String dbEndPoint, String dbName) throws SQLException
    {
        theConnection = connectionMgr.openConnection(dbEndPoint, dbName);
        
        return theConnection;
    }

    int writeLine(String lineToWrite)
    {
        int result = 0;
        fileId = 1;
        try
        {
            PreparedStatement ps = theConnection.prepareStatement(SQL_INSERT_LINE);
            ps.setInt(1, fileId);
            ps.setString(2, lineToWrite);
            ps.executeUpdate();
            
            ps = theConnection.prepareStatement(SQL_LINE_COUNT);
            ResultSet rs = ps.executeQuery();
            while (rs.next()){
                result = rs.getInt(1);
            }
            
        } catch (SQLException ex)
        {
            Logger.getLogger(DBConnector.class.getName()).log(Level.SEVERE, null, ex);
        }
        return result;
    }

}

DBConnectorTest - suitable for a build pipeline and does not break the tenets of unit testing

Code Block
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.celestial.mockito.filetodb;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import static org.junit.Assert.*;
import org.junit.Test;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import static org.mockito.Mockito.spy;

/**
 *
 * @author selvy
 */
public class DBConnectorTest
{
    static String dbEndPoint = "jdbc:mysql://localhost:3306/";
    static String dbName = "files";
    static Connection sqlConn = Mockito.mock(Connection.class);
    // Mockito does not like the line below to be present in each test method
    // it registers the static method with the current test thread once, if this line
    // is called more than once in a unit test file, a Mockito exception is thrown
    //
    // The live DB connection code has been moved to it's own test file because
    // it was pickup the mocked DriverManager and returning null because the 
    // params being passed in (real values) did not match the stubbed values
    
    static MockedStatic<DriverManager> mockedDMgr = Mockito.mockStatic(DriverManager.class);
    
    @BeforeClass
    static public  void    beforeAllTestsDo()
    {
        // Because we are mocking the static, registration of the method should
        // only occur once for the unit test file
        mockedDMgr.when(() -> DriverManager.getConnection(dbEndPoint+dbName, "root", "ppp")).thenReturn(sqlConn);
    }

     @Test
     public void open_connection_mocked() throws SQLException 
     {
         // arrange
         IDbConnectionMgr connection = (endPoint, db) -> {
             Connection conn = DriverManager.getConnection(endPoint+db, "root", "ppp");
             return conn;
         };
         DBConnector cut = new DBConnector(connection);
         DBConnector spyCut = spy(cut);
         int expectedLineNoCount = 1;
         Mockito.doReturn(1).when(spyCut).queryLinesInDB();
        // To
use a different type of file system loader, pass a// lambdaact
to loadFile()     // as shown hereConnection conn = spyCut.openConnection(dbEndPoint, dbName);
/*         int bytesReadlineNoResult = cutspyCut.loadFile(getLineCount(fname);
->         {
         // assert
List<String> result = null;      assertNotNull(conn);
     try    assertEquals(expectedLineNoCount, lineNoResult);
     }
{
     @Test
     public void write_line_to_mocked_connection() throws SQLException 
result  = Files.readAllLines(Paths.get(fname), StandardCharsets.UTF_8); {
         // }arrange
         IDbConnectionMgr connection catch= (IOExceptionendPoint, edb) -> {}
            return result;Connection conn = DriverManager.getConnection(endPoint+db, "root", "ppp");
   });     */     return conn;
// Redesign the FileLoader so that the machenism to load};
files up can be      DBConnector //cut passed= in as a lambda - still titghtly coupled the file systemnew DBConnector(connection);
         
    @Test     DBConnector publicspyCut void load_all_of_file_using_inbuilt_Files_type_as_lambda()= spy(cut);
      {   Mockito.doReturn(0).when(spyCut).queryLinesInDB();
      //   arrange
         StringPreparedStatement fileToLoadps = "c:/tmp/KeyboardHandler.txt" Mockito.mock( PreparedStatement.class );
         FileLoader cut = new FileLoader(fileToLoad);Mockito.when(sqlConn.prepareStatement(Mockito.any())).thenReturn(ps);
         
   int expectedBytesRead = 10;    //1371spyCut.openConnection(dbEndPoint, dbName);
         List<String>String pretendFileContentlineToWrite = new ArrayList<>() "be not changed by...";
         pretendFileContent.add("Hello") int expectedLineNo = 1;
         pretendFileContent.add("world");
         MockedStatic<Files>// ffact
= Mockito.mockStatic(Files.class);        //  ff.when(() -> Files.readAllLines(Paths.get(fileToLoad), StandardCharsets.UTF_8)).thenReturn(pretendFileContent);
         The idea of writeLine() is that achg time a line is written to the DB
         // actit's line No should be returned.  Line Nos start intat bytesRead1
= cut.loadFile((fname) ->       int lineNo  {= spyCut.writeLine(lineToWrite);
         
  List<String> result = null;    // assert
       try  assertEquals(expectedLineNo, lineNo);
     }

}

DBConnector - refactored

Code Block
/*
 {* To  change this license header, choose License Headers in Project Properties.
 * To change resultthis = Files.readAllLines(Paths.get(fname), StandardCharsets.UTF_8);
            }
            catch (IOException e){}
            return result;
         });
         
         // assert
         assertEquals(expectedBytesRead, bytesRead);
     }

     // Redesign has worked so this test uses a stub to simulate a file being
     // loaded, it is passed in as lambda - We've decoupled ourselves from the 
     // filesystem
     @Test
     public void load_all_of_file_via_stub() 
     {
         // arrange
 template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.celestial.mockito.filetodb;

import com.mysql.cj.xdevapi.PreparableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author selvy
 */
public class DBConnector
{
    private final IDbConnectionMgr connectionMgr;
    private Connection theConnection;
    private int fileId = 1;
    private int itsLineCount;
    private static final String SQL_INSERT_LINE = "insert into files.line_in_file" +
         " values (default, ?, ?);";
    private static final String SQL_LINE_COUNT = "select count(*) from files.line_in_file;";
    
    DBConnector(IDbConnectionMgr connection)
    {
        connectionMgr = connection;
    }

    Connection  openConnection(String dbEndPoint, String dbName) throws SQLException
    {
       String fileToLoadtheConnection = "c:/tmp/KeyboardHandler.txt";connectionMgr.openConnection(dbEndPoint, dbName);
        
   FileLoader cut     itsLineCount = new FileLoaderqueryLinesInDB(fileToLoad);
        
int    expectedBytesRead = 10;  return theConnection;
    }
    
    public  //int act    getLineCount()
    {
int bytesRead = cut.loadFile((fname) ->    return itsLineCount;
    {}
    
    public  void List<String> result = new ArrayList<>updateLineCount();
    {
        updateLineCount(getLineCount() + 1);
    }
    result.add("Hello");
    public  void    updateLineCount(int  result.add("world");value)
    {
        itsLineCount return= resultvalue;
    }
    
});    public  int getFileId()
    {
       // assertreturn fileId;
    }

  assertEquals(expectedBytesRead, bytesRead); int queryLinesInDB()
   } {
        try
  }

FileLoader implementation code - refactored

Code Block
/*  * To change this license{
header, choose License Headers in Project Properties.  * To change this templatePreparedStatement file,ps choose Tools | Templates
 * and open the template in the editor.
 */
package com.celestial.mockito.filetodb;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;


/**
 *
 * @author selvy
 */
public class FileLoader 
{
    class   IntWrapper
    {= theConnection.prepareStatement(SQL_LINE_COUNT);

            ResultSet rs = ps.executeQuery();
            while (rs.next()){
                itsLineCount = rs.getInt(1);
           int value;}
    }        
 String fileToLoad;     List<String> lines} =catch Collections.emptyList(SQLException ex);
      public FileLoader(String fileToLoad){
     {         this.fileToLoad = fileToLoadLogger.getLogger(DBConnector.class.getName()).log(Level.SEVERE, null, ex);
    }      public List<String> getLines() {}
         return linesitsLineCount;
    }

    public    int loadFilewriteLine(String fnamelineToWrite)
    {
{        int fileId  try= getFileId();
        try
  {      {
      lines = Files.readAllLines(Paths.get(fname), StandardCharsets.UTF_8);   PreparedStatement ps = theConnection.prepareStatement(SQL_INSERT_LINE);
  }         catch ps.setInt(IOException1, efileId);
        {    ps.setString(2, lineToWrite);
   }          return calculateFileSizeps.executeUpdate();
     }       
       int loadFile(ILoader func)   updateLineCount();
  {         lines =
func.loadFile(fileToLoad);        } returncatch calculateFileSize(SQLException ex);
     }   {
          private int calculateFileSize() Logger.getLogger(DBConnector.class.getName()).log(Level.SEVERE, null, ex);
    {    }
    IntWrapper  result = newreturn IntWrappergetLineCount();
    }
}

Interfaces

Code Block
/*
 * To change this license header, choose License Headers in lines.forEach(line -> {
                result.value += line.length();
        });
        
Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.celestial.mockito.filetodb;

import java.util.List;

/**
 *
 * @author selvy
 */
@FunctionalInterface
public interface ILoader 
{
    List<String>   return result.value;
    }
} loadFile(String fname);
}