How to write a test class for an apex trigger - salesforce

Can someone explain to me how to write a test class for an apex trigger like the following one?
trigger LeadAssignmentTrigger on Broker__c (before insert,before update)
{
List<Broker__c > leadsToUpdate = new List<Broker__c >();
for (Broker__c broker: Trigger.new)
{
if (broker.Referral_ID__c!= NULL)
{
String str = broker.Referral_ID__c;
Integer ln = str.Length();
String likeStr = '%'+str.subString(ln-10, ln-7)+'%'+str.subString(ln-7, ln-4) +'%'+ str.subString(ln-4);
// Find the sales rep for the current zip code
List<User> zip = [select Id from User
where MobilePhone Like : likeStr];
// if you found one
if (zip.size() > 0)
{
//assign the lead owner to the zip code owner
broker.OwnerId = zip[0].Id;
leadsToUpdate.add(broker);
}
else
{
// Throw Error
broker.addError('Invalid Referrel ID');
}
}
}
}
I am new to salesforce.Anyone help me to how to write apex class(test class) for above trigger.
#isTest
private class LeadAssignmentTriggerTest
{
static testMethod void validateHelloWorld()
{
User userObj = new User( Id = UserInfo.getUserId() );
userObj.MobilePhone = '5555555555';
update userObj
test.startTest();
try
{
Broker__c broker = new Broker__c();
broker.Referral_ID__c = '55555555';
broker.City ='New York';
// Add all required field here
insert broker;
}
Catch(Exception ee)
{
}
test.stopTest();
}
}
AccountBrowseExtensionTesttestAccountBrowseSystem.DmlException: Insert failed. First exception on row 0; first error: FIELD_CUSTOM_VALIDATION_EXCEPTION, City is mandatory: []
Stack Trace: Class.AccountBrowseExtensionTest.testAccountBrowse: line 20, column 1
CloseActivityControllerTesttestCloseActivitySystem.DmlException: Insert failed. First exception on row 0; first error: FIELD_CUSTOM_VALIDATION_EXCEPTION, City is mandatory: []
Stack Trace: Class.CloseActivityControllerTest.testCloseActivity: line 13, column 1
changeOwnerControllerTesttestchangeOwnerSystem.DmlException: Insert failed. First exception on row 0; first error: FIELD_CUSTOM_VALIDATION_EXCEPTION, City is mandatory: []
Stack Trace: Class.changeOwnerControllerTest.testchangeOwner: line 20, column 1
cntactsclassTesttestcntactsSystem.DmlException: Insert failed. First exception on row 0; first error: FIELD_CUSTOM_VALIDATION_EXCEPTION, City is mandatory: []
Stack Trace: Class.cntactsclassTest.testcntacts: line 13, column 1
LogACallControllerTesttestLogACallSystem.DmlException: Insert failed. First exception on row 0; first error: FIELD_CUSTOM_VALIDATION_EXCEPTION, City is mandatory: []
Stack Trace: Class.LogACallControllerTest.testLogACall: line 14, column 1
RedirectControllerTesttestRedirectSystem.DmlException: Insert failed. First exception on row 0; first error: FIELD_CUSTOM_VALIDATION_EXCEPTION, City is mandatory: []
Stack Trace: Class.RedirectControllerTest.testRedirect: line 20, column 1
TestAccountSharetestSystem.DmlException: Insert failed. First exception on row 0; first error: FIELD_CUSTOM_VALIDATION_EXCEPTION, Mobile Number is mandatory after Appointment is fixed.: []
Stack Trace: Class.TestAccountShare.test: line 40, column 1

You are writing a trigger for Broker__c on before insert and before update
So since it's a trigger, your code will run every time you insert or update a record.
To write a test class simply create two test methods:
one to insert a Broker__c record
one to update a Broker__c record
Check here on how to create test classes
By the way you should check the best coding practices on how to write a better trigger and handlers here
Edit:
You should also remove the SOQL inside the loop and create a Map with your query outside the for loop

Related

Every other query fails with syntax error

I use CockroachDb and Npgsql driver.
I have a simple Users table. When I insert new record, every other query fails with syntax error, which seems bizarre to me.
CREATE TABLE Users (
RequestIdentifier BYTEA NOT NULL UNIQUE,
Identifier UUID PRIMARY KEY DEFAULT gen_random_uuid(),
Id INT8 NOT NULL DEFAULT unique_rowid(),
Email BYTEA NOT NULL UNIQUE,
Username BYTEA NOT NULL,
PasswordHash BYTEA NOT NULL
);
var q = #"
INSERT INTO Users (RequestIdentifier, Email, Username, PasswordHash)
VALUES (#RequestIdentifier, #Email, #Username, #PasswordHash)
ON CONFLICT (RequestIdentifier)
DO NOTHING
RETURNING Identifier
";
byte[] userIdentifier = null;
using (var cmd = new NpgsqlCommand(q, dbConn)) {
cmd.Parameters.Add("RequestIdentifier", NpgsqlDbType.Bytea);
cmd.Parameters.Add("Email", NpgsqlDbType.Bytea);
cmd.Parameters.Add("Username", NpgsqlDbType.Bytea);
cmd.Parameters.Add("PasswordHash", NpgsqlDbType.Bytea);
await cmd.PrepareAsync();
cmd.Parameters[0].Value = msg.RequestIdentifier;
cmd.Parameters[1].Value = msg.Email;
cmd.Parameters[2].Value = msg.Username;
cmd.Parameters[3].Value = passwordHash;
try {
userIdentifier = ((Guid) await cmd.ExecuteScalarAsync()).ToByteArray();
} catch (PostgresException e) when (e.SqlState == SqlErrorCodes.UniqueViolation) {
logger.Information("Email {Email} already in use", UTF8.GetString(msg.Email));
} catch (PostgresException e) {
logger.Error("{Exception}", e);
throw;
}
}
Npgsql.PostgresException (0x80004005): 42601: at or near "close": syntax error
at Npgsql.NpgsqlConnector.<>c__DisplayClass160_0.<<DoReadMessage>g__ReadMessageLong|0>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at Npgsql.NpgsqlConnector.<>c__DisplayClass160_0.<<DoReadMessage>g__ReadMessageLong|0>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at Npgsql.NpgsqlConnector.<>c__DisplayClass160_0.<<DoReadMessage>g__ReadMessageLong|0>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at Npgsql.NpgsqlDataReader.NextResult(Boolean async, Boolean isConsuming)
at Npgsql.NpgsqlCommand.ExecuteReaderAsync(CommandBehavior behavior, Boolean async, CancellationToken cancellationToken)
at Npgsql.NpgsqlCommand.ExecuteScalar(Boolean async, CancellationToken cancellationToken)
at OwlAuthentication.Services.CockroachDbStorageService.CreateOrRetrieveExistingUser(SignUpMessage msg) in C:\Users\Che Khen Kho\Desktop\dotnet\OwlAuthentication\Services\CockroachDbStorageService.cs:line 94
Exception data:
Severity: ERROR
SqlState: 42601
MessageText: at or near "close": syntax error
Detail: source SQL:
CLOSE ALL
^
File: lexer.go
Line: 175
Routine: Error
If I try, say, 10 queries, 5 of them would fail with this exception, but quite often more than 5 rows would actually get inserted (sometimes 6, sometimes 8, etc.).
I tested it with PostgreSQL as well (using uuid_generate_v4 instead of gen_random_uuid and BIGSERIAL for Id column), and everything works fine.
I think this is covered by this issue (https://github.com/cockroachdb/cockroach/issues/45490) which is fixed in CockroachDB v20.1

Sales force trigger INVALID_FIELD_FOR_INSERT_UPDATE

Below code works fine when updating/inserting on Visual Force Page one record at a time, receive error 'Insert failed....INVALID_FIELD_FOR_INSERT_UPDATE, cannot specify Id in an insert call' when using data loader (error pointing to code, 'insert amcRecord'). Would anyone know how to fix?
trigger Status on Cl__c (after insert, after update) 
{
List<AMC__c> amcRecord = new List<AMC__c>();
for (Cl__c cls: Trigger.new ) 
{
    Cl__c oldcls = Trigger.oldMap.get(cls.Id);
    if (cls.Status__c == 'Completed' && (oldcls.Status__c != 'Completed'  )) 
{
     AMC__c newAMC = new AMC__c();
     newAMC.Cl__c = cls.ID;
     newAMC.Default__c = true;  
     amcRecord.add(newAMC); 
     insert amcRecord;
}
  }
}
amcRecord is a list, not a single item, but you're calling insert for every item in the request [which would only be 1 for the UI, but upto 200 with the data loader. You need to move the insert amcRecord line to after the for (... Trigger.new) loop has finished, so that its only called once, e.g.
trigger Status on Cl__c (after insert, after update)
{
List<AMC__c> amcRecord = new List<AMC__c>();
for (Cl__c cls: Trigger.new )
{
Cl__c oldcls = Trigger.oldMap.get(cls.Id);
if (cls.Status__c == 'Completed' && (oldcls.Status__c != 'Completed' ))
{
AMC__c newAMC = new AMC__c();
newAMC.Cl__c = cls.ID;
newAMC.Default__c = true;
amcRecord.add(newAMC);
}
}
insert amcRecord;
}
You shouldn't be performing DML inside a loop.This exception comes if you try to insert already inserted record.
Account a =new Account();
a.name='Hello';
insert a;
insert a;
Changed
insert amcRecord;
to
upsert amcRecord;
It seems to be working now.

Why does Oracle ODP.Net automattically commit inserted records when using parameter array binding?

Strange issue. I have a managed OPD.Net application that calls a stored procedure to insert records. When I call the procedure normally and rollback the transaction, the records are not saved to the table (duh!). When I use the procedure with parameter array binding and still roll back the transaction, the records ARE saved to the table. Somehow, the records are getting committed even though I do a rollback!
TEST Schema:
CREATE TABLE TEST
(
ID NUMBER(15,0),
VALUE VARCHAR2(50 CHAR)
)
/
CREATE SEQUENCE TEST_ID_SEQ
INCREMENT BY 1
START WITH 50
MINVALUE 1
MAXVALUE 999999999999999
NOCYCLE
NOORDER
CACHE 100
/
CREATE OR REPLACE PROCEDURE TEST_INSERT
(
iVALUE IN VARCHAR2,
oID OUT NUMBER
)
AS
BEGIN
oID := TEST_ID_SEQ.NEXTVAL;
INSERT INTO TEST
(
ID,
VALUE
)
VALUES
(
oID,
iVALUE
);
END;
/
Test Code:
using Oracle.ManagedDataAccess.Client;
using Oracle.ManagedDataAccess.Types;
using System;
using System.Data;
namespace OdpTestArrayBinding
{
class Program
{
private const string cConnectioString = "Data Source=DB_DEV;User Id=TMP;Password=sqlsql";
static void Main(string[] args)
{
using (OracleConnection lConnectionA = new OracleConnection(cConnectioString))
{
lConnectionA.StateChange += ConnectionStateChanged;
lConnectionA.Open();
Console.WriteLine($"[Connection={lConnectionA.GetHashCode()}] Connection opened.");
int lStartCount = CountTestTableRows(lConnectionA);
Console.WriteLine($"[Connection={lConnectionA.GetHashCode()}] Number of rows in table at start is {lStartCount}.");
using (OracleTransaction lTransaction = lConnectionA.BeginTransaction())
{
Console.WriteLine($"[Connection={lConnectionA.GetHashCode()}] Transaction started.");
try
{
using (OracleCommand lCmd = new OracleCommand())
{
lCmd.Connection = lConnectionA;
lCmd.BindByName = true;
lCmd.CommandType = System.Data.CommandType.StoredProcedure;
lCmd.CommandText = "TEST_INSERT";
lCmd.Parameters.Add("iVALUE", OracleDbType.Varchar2, ParameterDirection.Input);
// The OracleDbType of the output does not seem to matter, the actual value is always OracleDecimal
lCmd.Parameters.Add("oID", OracleDbType.Int64, ParameterDirection.Output);
lCmd.ArrayBindCount = 3;
lCmd.Parameters["iVALUE"].Value = new string[] { "Foo", "Bar", "Boo" };
// Not required.
//lCmd.Parameters["oID"].Value = new long[] { -1, -1, -1 };
lCmd.ExecuteNonQuery();
OracleDecimal[] lOutIds = (OracleDecimal[])lCmd.Parameters["oID"].Value;
Console.WriteLine($"[Connection={lConnectionA.GetHashCode()}] Inserted 3 rows using stored procedure, out ID vales are {string.Join(",", lOutIds)}.");
}
ListRows(lConnectionA, lStartCount + 3);
using (OracleConnection lConnectionB = new OracleConnection(cConnectioString))
{
lConnectionB.StateChange += ConnectionStateChanged;
lConnectionB.Open();
Console.WriteLine($"[Connection={lConnectionB.GetHashCode()}] Connection opened.");
ListRows(lConnectionB, lStartCount);
}
}
finally
{
lTransaction.Rollback();
Console.WriteLine($"[Connection={lConnectionA.GetHashCode()}] Transaction rolled back.");
}
}
}
Console.WriteLine("Press the ENTER key to continue...");
Console.ReadLine();
}
private static void ConnectionStateChanged(object sender, StateChangeEventArgs e)
{
Console.WriteLine($"[Connection={sender.GetHashCode()}] State changed from {e.OriginalState} to {e.CurrentState}.");
}
private static int CountTestTableRows(OracleConnection aConnection)
{
using (OracleCommand lCmd = new OracleCommand())
{
lCmd.Connection = aConnection;
lCmd.BindByName = true;
lCmd.CommandType = System.Data.CommandType.Text;
lCmd.CommandText = "SELECT COUNT(*) FROM TEST";
return Convert.ToInt32(lCmd.ExecuteScalar());
}
}
private static void ListRows(OracleConnection aConnection, int aExpectedRowCount)
{
int lCount = CountTestTableRows(aConnection);
Console.Write($"[Connection={aConnection.GetHashCode()}] Number of rows in table {lCount}");
if (lCount == aExpectedRowCount)
Console.WriteLine(" (Test passed, actual and expected row count are the same).");
else
Console.WriteLine($" (Test FAILED!, expected {aExpectedRowCount} rows).");
}
}
}
First run console output:
[Connection=47973293] State changed from Closed to Open.
[Connection=47973293] Connection opened.
[Connection=47973293] Number of rows in table at start is 0.
[Connection=47973293] Transaction started.
[Connection=47973293] Inserted 3 rows using stored procedure, out ID vales are 50,51,52.
[Connection=47973293] Number of rows in table 3 (Test passed, actual and expected row count are the same).
[Connection=21040294] State changed from Closed to Open.
[Connection=21040294] Connection opened.
[Connection=21040294] Number of rows in table 3 (Test FAILED!, expected 0 rows).
[Connection=21040294] State changed from Open to Closed.
[Connection=47973293] Transaction rolled back.
[Connection=47973293] State changed from Open to Closed.
I have tried Oracle.ManagedDataAccess.dll 4.121.2.20141216 (ODAC RELEASE 3) and 4.121.2.20150926 (ODAC RELEASE 4), both give the same result. Any ideas or workarounds?
Can you try to change your code to use this example.
Open only a using block for your connection.
public void RunOracleTransaction(string connectionString)
{
using (OracleConnection connection = new OracleConnection(connectionString))
{
connection.Open();
OracleCommand command = connection.CreateCommand();
OracleTransaction transaction;
// Start a local transaction
transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted);
// Assign transaction object for a pending local transaction
command.Transaction = transaction;
try
{
command.CommandText =
"INSERT INTO Dept (DeptNo, Dname, Loc) values (50, 'TECHNOLOGY', 'DENVER')";
command.ExecuteNonQuery();
command.CommandText =
"INSERT INTO Dept (DeptNo, Dname, Loc) values (60, 'ENGINEERING', 'KANSAS CITY')";
command.ExecuteNonQuery();
transaction.Commit();
Console.WriteLine("Both records are written to database.");
}
catch (Exception e)
{
transaction.Rollback();
Console.WriteLine(e.ToString());
Console.WriteLine("Neither record was written to database.");
}
}
}
Pleaase check your command : you have forget assign the transaction on your command?
lCmd.Connection = lConnectionA;
// Assign transaction to your command
lCmd.Transaction = lTransaction;
You should create a TransactionScope that wraps your database connection and within your TransactionScope, try enlisting in the ambient transaction:
<your connection object>.EnlistTransaction(Transaction.Current);

Add value into database plus 1

In my database I have:
Row ID - Driver ID - Log ID.
Row ID is unique and auto-increments. What I want is for the Log ID to be unique for each row that has that Driver ID.
For example say a row is inserted with Driver ID 1 I want that row to have a Log ID of 1 but the next time a row is inserted with Driver ID 1 I want it to have a Log ID of 2.
How can I achieve this?
By way for database i am using PHPMyAdmin.
----------------Edit----------------------
This is what i have in my PHP now, but it says:
On the webpage: Incorrect integer value: '' for column 'FinesCost' at row 1
And i dump the variables and get this: string(2) "16" string(2) "16" string(2) "16" so i dont understand why it is saying incorrect integer value and why it is saying they are undefines because they are very clearly defined.
In the PHP error log: [19-Jul-2013 10:44:18 Europe/Minsk] PHP Notice: Undefined variable: FinesCostP‌ost2 in C:\inetpub\wwwroot\hosting\Dan\JWT\drivers-log-send.php on line 336
[19-Jul-2013 10:44:18 Europe/Minsk] PHP Notice: Undefined variable: TravelExpensesPo‌​st2 in C:\inetpub\wwwroot\hosting\Dan\JWT\drivers-log-send.php on line 336
///PHP TO INSERT DRIVER'S BANK DETAILS INTO BANK DATABASE
session_start();
$host=""; // Host name
$username=""; // Mysql username
$password=""; // Mysql password
$db_name=""; // Database name
$tbl_name="jwtdriversbank"; // Table name
$un = "";
$usrname = "";
$usrpass = "";
$userID = "";
mysql_connect("$host", "$username", "$password")or die("cannot connect");
mysql_select_db("$db_name")or die("cannot select DB");
if(isset ($_SESSION['usrName']))
{
$usrname = $_SESSION['usrName'];
}
else
{
echo "4";
}
//var_dump ($usrname);
if(isset ($_SESSION['usrPass']))
{
$usrpass = $_SESSION['usrPass'];
}
else
{
echo "5";
}
$sql="SELECT * FROM jwtdrivers WHERE username='$usrname' and password='$usrpass'";
$result=mysql_query($sql);
$rows=mysql_fetch_array($result);
$userID = $rows['id'];
//var_dump ($userID);
if($userID == "")
{
echo "3";
}
else
{
$TotalProfitPost = $TotalProfit;
$LateFeePost = $LateFee;
$FinesCostPost2 = $FinesCost;
$TravelExpensesPost2 = $TravelExpenses;
$FuelCostPost = $FuelCost;
$CargoDamagePost = $CargoDamage;
$TruckDamagePost = $TruckDamage;
var_dump ($TotalProfitPost);
var_dump($FinesCostPost2);
var_dump($TravelExpensesPost2);
$sql="INSERT INTO jwtdriversbank2 (DriverID, LogID, TotalProfit, LateFee, FinesCost, TravelExpenses, FuelCost, CargoDamage, TruckDamage) VALUES ('$userID', COALESCE((Select MAX(LogID) from jwtdriversbank2 tab2 where tab2.DriverID = '$userID'),0)+1,'$TotalProfitPost','$LateFeePost', '$FinesCostP‌ost2' , '$TravelExpensesPo‌​st2' ,'$FuelCostPost','$CargoDamagePost','$TruckDamagePost')";
$result = mysql_query($sql);
if($result)
{
}
else
{
die(mysql_error());
}
}
Add a primary key for the two columns.
It should do the trick.
Look at this link for help
ALTER TABLE table_name
ADD CONSTRAINT pk_DriverID PRIMARY KEY (DriverID,LogID)
Do not forget to drop the first primary key because you will not need it no more.
EDIT : COMPLETE WITH THE OTHER ANSWER
Here is the code to insert your data.
Insert into <table_name>
values p_RowID, p_DriverID, COALESCE((Select MAX(Log_id) from <table_name> tab2 where tab2.Driver_id = p_DriverID),0)+1;
That should close the question.
You did not defined variable because PHP can't read them.
I opened your program inside VIM editor and I found "<200c>" char inside $FineCostPost2 in the SQL query. You have to change it to make it work.
A quick solution would be to use a subquery to find the maximum log (last log id) then increment it, something like this
Insert into <table_name>
values p_RowID, p_DriverID, COALESCE((Select MAX(Log_id) from <table_name> tab2 where tab2.Driver_id = p_DriverID),0)+1;
Here p_RowID and p_DriverID are the values you pass to insert into your table. The Coalesce function would check the given value and if it is NULL then it would replace it with the second parameter, in this case 0

Codeigniter, error tracking in transaction

I'm running a little method in CodeIgniter to insert some lines in the database (same table). I would like to see which insertion have failed inside a transaction (by returning an array of titles). My code is :
$failure = array(); //the array where we store what failed
$this->db->trans_start();
foreach ($data as $ressourceCsv){ //data is an array of arrays to feed the database
$this->ajout_ressource($ressourceCsv); //method to insert (basically, just an insert with active record)
if (($this->db->_error_message())!=null) {
$failure[] = $ressourceCsv['title'];
}
}
$this->db->trans_complete();
return $failure;
The fact is that if I don't make it a transaction (no $this->db->trans_...), it works perfectly and I have an array containing a few titles. But with the transaction, the array contains every titles since the first error. Is there a way to just get the title from the insertion which caused the transaction to rollback?
I have also tried with :
$failure = array(); //the array where we store what failed
$this->db->trans_start();
foreach ($data as $ressourceCsv){ //data is an array of arrays to feed the database
if (!$this->ajout_ressource($ressourceCsv)) { //active record insertion return true
$failure[] = $ressourceCsv['title']; // if successful
}
}
$this->db->trans_complete();
return $failure;
I believe that once an error occurs inside a transaction, you must rollback before any more DB mods can be made. That would explain the behavior you are seeing. After the first error, the transaction is "aborted" and you continue your loop, causing every subsequent SQL command to fail as well. This can be illustrated as follows:
db=# select * from test1;
id | foo | bar
----+-----+-----
(0 rows)
db=# begin;
BEGIN
db=# insert into test1 (foo, bar) values (1, 'One');
INSERT 0 1
db=# insert into test1 (foo, bar) values (Oops);
ERROR: column "oops" does not exist
LINE 1: insert into test1 (foo, bar) values (Oops);
^
db=# insert into test1 (foo, bar) values (2, 'Two');
ERROR: current transaction is aborted, commands ignored until end of transaction block
db=# select * from test1;
ERROR: current transaction is aborted, commands ignored until end of transaction block
db=# commit;
ROLLBACK
ace_db=# select * from test1;
id | foo | bar
----+-----+-----
(0 rows)
db=#
Note it seems that "commit" does a "rollback" if there was an error (it was not a typo.)
Also BTW: use $this->db->trans_status() === FALSE to check for an error during the transaction.
Update: Here's some (untested) code to do it in a transaction so that the inserts are not seen by others until you are ready:
$failure = array(); //the array where we store what failed
$done = false;
do {
$this->db->trans_begin();
foreach ($data as $key => $ressourceCsv){ //data is an array of arrays to feed the database
$this->ajout_ressource($ressourceCsv); //method to insert (basically, just an insert with active record)
if ($this->db->trans_status() === false) { // an insert failed
$failure[] = $ressourceCsv['title']; // save the failed title
unset($data[$key]); // remove failed insert from data set
$this->db->trans_rollback(); // rollback the transaction
break; // retry the insertion
}
}
$done = true; // completed without failure
} while (count($data) and ! $done); // keep going until no data or success
/*
* Two options (uncomment one):
* 1. Commit the successful inserts even if there were failures.
$this->db->trans_commit();
* 2. Commit the successful inserts only if no failures.
if (count($failure)) {
$this->db->trans_rollback();
} else {
$this->db->trans_commit();
}
*/
return $failure;

Resources