chevron-thin-right chevron-thin-left brand cancel-circle search youtube-icon google-plus-icon linkedin-icon facebook-icon twitter-icon toolbox download check linkedin phone twitter-old google-plus facebook profile-male chat calendar profile-male
0 votes
I have this method:

Public Sub StoreMortalities
    TransactionDataProvider.Instance().Add(newTransaction)
    Try
        InsertMortalities(mortalitiesToInsert, newTransaction)
    Catch ex As Exception
        'New transacion must be marked as deleted if the storing of its children fails
        TransactionDataProvider.Instance().MarkAsDeleted(newTransaction.TransactionId.Value)
        Throw
    End Try
End Sub


And I have this test code (reduced):

Dim recorder As New RecordExpectations
Try
    TransactionDataProvider.Instance().Add(Nothing)

    ' When the method tries to store a mortality we throw an exception to see if it handles it gracefuly
    MortalityDataProvider.Instance().Store(Nothing)
    recorder.Throw(New Exception)

     ' The method should mark the new transaction as deleted
     TransactionDataProvider.Instance().MarkAsDeleted(1)
Finally
     recorder.Dispose()
End Try


The problem is that the TransactionDataProvider.Instance().Add() method stores the transaction in the database and sets its TransactionID property (which is an identity), so my test fails in the TransactionDataProvider.Instance().MarkAsDeleted() method because the ID is null. How can I modify the code to set the property's value in the object that is passed to the TransactionDataProvider.Instance().Add() method?

I hope it's clear, thanks in advance.
asked by Julian (1.8k points)

10 Answers

0 votes
Hi,
I am not sure that I understand but perhaps you need to mock newTransaction.TransactionId.Value
answered by scott (32k points)
0 votes
Thank you for replying. How could I mock newTransaction? The object is instantiated directly in the tested method:

Dim newTransaction As TransactionInfo
' Create the new transaction
newTransaction = New TransactionInfo( _
    barn.OperationUnitId, _
    m_Info.FocusDate, _
    Enumerations.TransactionType.DailyRegistration, _
    m_Info.CurrentUser, _
    m_Info.ResponsibleEmployeeId)
    TransactionDataProvider.Instance().Add(newTransaction)
Try
    InsertMortalities(mortalitiesToInsert, newTransaction)
Catch ex As Exception                     
    TransactionDataProvider.Instance().MarkAsDeleted(newTransaction.TransactionId.Value)
    Throw
End Try


I tried several things like adding a "Dim mockedTransaction as New TransactionInfo" in the expectations' recording, and also using MockManager.Mock(GetType("TransactionInfo")) because it's supposed to mock the next instance of the type, but it didn't seem to work.
answered by Julian (1.8k points)
0 votes
Julian,
This is EXACTLY TypeMock's biggest added value.
TypeMock can mock a 'future instance'
Here is how:
Dim recorder As New RecordExpectations
Try
    ' mock the next future instance TransactionInfo
    Dim mockedTransaction As New TransactionInfo()
    ' value should be 1
    recorder.ExpectAndReturn(
       mockedTransaction.TransactionId.Value,1).RepeatAlways()
Finally
     recorder.Dispose()
End Try


I used RepeatAlways to indicate that we don't really care how many times this method is called.

Use the Tracer if this doesn't work, you might have other TransactionInfo instances, or you might use newTransaction.TransactionId multiple times you will then need to make the repeat always a default:
recorder.DefaultBehavior.RepeatAlways()
recorder.ExpectAndReturn(mockedTransaction.TransactionId.Value,1)
answered by scott (32k points)
0 votes
Thank you! It finally worked, although I had to make some modifications. This is my final test code:
<TestAttribute(), ExpectedException(GetType(UnitTestGeneratedException))> _
Public Sub TestSaveTransaction_NewTransactionIsMarkedAsDeletedOnError()

    ' We will throw this exception when the method tries to save the mortality
    Dim exception As New UnitTestGeneratedException

    ' Set the expectations
    Dim recorder As New RecordExpectations
    Try
        ' The method will need to instantiate a TransactionInfo object
        Dim mockedTransaction As New TransactionInfo
        ' The method will try to access the TransactionID property
        ' If we don't set this a SqlNullValueException exception will be thrown
        recorder.ExpectAndReturn(mockedTransaction.TransactionId, Constants.SqlInt32_1).RepeatAlways()

        TransactionDataProvider.Instance().Add(Nothing)

        ' When the method tries to store a mortality we throw an exception
        ' to see if it catches it to delete the transaction
        MortalityDataProvider.Instance().Store(Nothing)
        recorder.Throw(exception)

        ' The method should mark the new transaction as deleted
        TransactionDataProvider.Instance().MarkAsDeleted(1)
    Finally
        recorder.Dispose()
    End Try

    ' Create some data to make the method work
    Dim barn As New BarnInfo
    barn.OperationUnitId = Constants.SqlInt32_1

    Dim mortalitiesToInsert As New MortalityInfoCollection
    Dim mortality As New MortalityInfo
    mortalitiesToInsert.Add(mortality)

    ' This is what we want to test
    m_MortalityController.SaveTransaction(barn, Nothing, mortalitiesToInsert, Nothing, Nothing)

End Sub


- I had to instantiate the exception outside the recording block.
- I had to return a SqlInt32 value instead of 1. By the way, I've found that trying to instantiate INullable types in the expectations recording makes TypeMock to fail.

Just because I'm curious: somewhere I saw that you can check the arguments that were passed to a mocked method. Could that be used to modify those objects?
answered by Julian (1.8k points)
0 votes
Julian,
We are happy to help. Thanks for posting the final code.

By the way, I've found that trying to instantiate INullable types in the expectations recording makes TypeMock to fail.

I have checked this and there is a problem returning Null to Nullable Types. We will fix this. Did you have a different scenario?

Just because I'm curious: somewhere I saw that you can check the arguments that were passed to a mocked method. Could that be used to modify those objects?

It is possible to check the arguments by using CheckArguments
It is possible to modify the arguments using Assign

Dim recorder As New RecordExpectations
Try
   ' Mock Add
   TransactionDataProvider.Instance().Add(Nothing)
   ' Swap the Transaction with a dummy transaction created before
   recorder.CheckArguments(New Assign(dummyTransaction))
   ' We can also VALIDATE that the correct argument was passed using
   ' recorder.CheckArguments(New Assign(dummyTransaction).AndCheck())
Finally
    recorder.Dispose()
End Try 
answered by scott (32k points)
0 votes
Scott,

Thank you again.
I have checked this and there is a problem returning Null to Nullable Types. We will fix this. Did you have a different scenario?

I created an small example to illustrate the issue with INullables (maybe this should be in a new post?):

Imports System.Data.SqlTypes
Imports MbUnit.Framework
Imports TypeMock

<TestFixture()> _
Public Class INullableTest

    <TestAttribute()> _
    Public Sub TestReturnSqlInt32()

        Dim recorder As New RecordExpectations
        Try
            DummyClass.DoWork()
            recorder.Return(New SqlInt32(1))
        Finally
            recorder.Dispose()
        End Try

        Dim value As SqlInt32 = DummyClass.DoWork()
        Assert.IsFalse(value.IsNull)

    End Sub

End Class

Public Class DummyClass

    Public Shared Function DoWork() As SqlInt32

    End Function

End Class

When I run the test I get an exception, this is the stack trace:

[failure] INullableTest.TestReturnSqlInt32
TestCase 'INullableTest.TestReturnSqlInt32'
failed: Index was out of range. Must be non-negative and less than the size of the collection.
Nombre del parámetro: index
   System.ArgumentOutOfRangeException
   Message: Index was out of range. Must be non-negative and less than the size of the collection.
   Parameter name: index
   Source: mscorlib
   StackTrace:
   at System.Collections.ArrayList.get_Item(Int32 index)
   at ar.a()
   at TypeMock.RecorderManager.a(Object A_0, Type A_1, Boolean A_2, String A_3)
   at TypeMock.RecorderManager.a(String A_0, String A_1, Object A_2, Type A_3, Boolean A_4)
   at TypeMock.RecorderManager.a(String A_0, String A_1, Object A_2, Object[] A_3, MethodBase A_4, Object A_5)
   at TypeMock.MockManager.a(String A_0, String A_1, Object A_2, Object A_3, Object[] A_4)
   at TypeMock.InternalMockManager.getReturn(Object that, String typeName, String methodName, Object methodParameters, Object p1)
   at System.Data.SqlTypes.SqlInt32..ctor(Int32 value)
   D:DocumentosVisual Studio ProjectsTestTypeMockINullableTest.vb(14,0): at TestTypeMock.INullableTest.TestReturnSqlInt32()

Hope this helps.
It is possible to check the arguments by using CheckArguments
It is possible to modify the arguments using Assign


Thank you, I'll try that :)
answered by Julian (1.8k points)
0 votes
Julian,
Thanks for pointing out this scenario.

I think that you want to return a new SqlInt32 and not a Mock of that type. So just create the SqlInt32 outside the recording block:
Dim returnValue As New SqlInt32(1)
Dim recorder As New RecordExpectations
Try
   DummyClass.DoWork()
   recorder.Return(returnValue)
Finally
   recorder.Dispose()
End Try


There is problem is with mocking new structures, as there are no instances for structures, we have to think what is supposed to happen in this scenario.

We will fixed this (and the incorrect message).
answered by scott (32k points)
0 votes
Scott
I think that you want to return a new SqlInt32 and not a Mock of that type. So just create the SqlInt32 outside the recording block:

That's what I do, but it's kind of weird, because I assumed that returning a value type shouldn't be a problem. In short, for me this:
DummyClass.DoWork()
recorder.Return(1) 

is conceptually the same as this:
DummyClass.DoWork()
recorder.Return(New SqlInt32(1)) 

There is problem is with mocking new structures, as there are no instances for structures, we have to think what is supposed to happen in this scenario.

IMHO the most natural thing is to try them like any other value type.
We will fixed this (and the incorrect message).

Changing the message should help, it's not friendly at all :P
Thank you again.
answered by Julian (1.8k points)
0 votes
Julian,
We have been thinking about this and we have added a feature request to support your scenario and to enable returning values without creating them outside the recording block. :D
answered by scott (32k points)
0 votes
Hi
This feature is available in 4.0.0 release
answered by ohad (35.4k points)
...